diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs index 737be6ba5e658..36eeb9360d607 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs @@ -561,6 +561,48 @@ public override Symbol VisitPointerType(PointerTypeSymbol symbol) return new PointerTypeSymbol(symbol.PointedAtTypeWithAnnotations.WithTypeAndModifiers(otherPointedAtType, otherModifiers)); } + public override Symbol VisitFunctionPointerType(FunctionPointerTypeSymbol symbol) + { + var sig = symbol.Signature; + + var otherReturnType = (TypeSymbol)Visit(sig.ReturnType); + if (otherReturnType is null) + { + return null; + } + + var otherRefCustomModifiers = VisitCustomModifiers(sig.RefCustomModifiers); + var otherReturnTypeWithAnnotations = sig.ReturnTypeWithAnnotations.WithTypeAndModifiers(otherReturnType, VisitCustomModifiers(sig.ReturnTypeWithAnnotations.CustomModifiers)); + + var otherParameterTypes = ImmutableArray.Empty; + ImmutableArray> otherParamRefCustomModifiers = default; + + if (sig.ParameterCount > 0) + { + var otherParamsBuilder = ArrayBuilder.GetInstance(sig.ParameterCount); + var otherParamRefCustomModifiersBuilder = ArrayBuilder>.GetInstance(sig.ParameterCount); + + foreach (var param in sig.Parameters) + { + var otherType = (TypeSymbol)Visit(param.Type); + if (otherType is null) + { + otherParamsBuilder.Free(); + otherParamRefCustomModifiersBuilder.Free(); + return null; + } + + otherParamRefCustomModifiersBuilder.Add(VisitCustomModifiers(param.RefCustomModifiers)); + otherParamsBuilder.Add(param.TypeWithAnnotations.WithTypeAndModifiers(otherType, VisitCustomModifiers(param.TypeWithAnnotations.CustomModifiers))); + } + + otherParameterTypes = otherParamsBuilder.ToImmutableAndFree(); + otherParamRefCustomModifiers = otherParamRefCustomModifiersBuilder.ToImmutableAndFree(); + } + + return symbol.SubstituteTypeSymbol(otherReturnTypeWithAnnotations, otherParameterTypes, otherRefCustomModifiers, otherParamRefCustomModifiers); + } + public override Symbol VisitProperty(PropertySymbol symbol) { return this.VisitNamedTypeMember(symbol, ArePropertiesEqual); @@ -736,6 +778,49 @@ private bool ArePointerTypesEqual(PointerTypeSymbol type, PointerTypeSymbol othe return AreTypesEqual(type.PointedAtType, other.PointedAtType); } + private bool AreFunctionPointerTypesEqual(FunctionPointerTypeSymbol type, FunctionPointerTypeSymbol other) + { + var sig = type.Signature; + var otherSig = other.Signature; + + ValidateFunctionPointerParamOrReturn(sig.ReturnTypeWithAnnotations, sig.RefKind, sig.RefCustomModifiers, allowOut: false); + ValidateFunctionPointerParamOrReturn(otherSig.ReturnTypeWithAnnotations, otherSig.RefKind, otherSig.RefCustomModifiers, allowOut: false); + if (sig.RefKind != otherSig.RefKind || !AreTypesEqual(sig.ReturnTypeWithAnnotations, otherSig.ReturnTypeWithAnnotations)) + { + return false; + } + + return sig.Parameters.SequenceEqual(otherSig.Parameters, AreFunctionPointerParametersEqual); + } + + private bool AreFunctionPointerParametersEqual(ParameterSymbol param, ParameterSymbol otherParam) + { + ValidateFunctionPointerParamOrReturn(param.TypeWithAnnotations, param.RefKind, param.RefCustomModifiers, allowOut: true); + ValidateFunctionPointerParamOrReturn(otherParam.TypeWithAnnotations, otherParam.RefKind, otherParam.RefCustomModifiers, allowOut: true); + + return param.RefKind == otherParam.RefKind && AreTypesEqual(param.TypeWithAnnotations, otherParam.TypeWithAnnotations); + } + + [Conditional("DEBUG")] + private static void ValidateFunctionPointerParamOrReturn(TypeWithAnnotations type, RefKind refKind, ImmutableArray refCustomModifiers, bool allowOut) + { + Debug.Assert(type.CustomModifiers.IsEmpty); + Debug.Assert(verifyRefModifiers(refCustomModifiers, refKind, allowOut)); + + static bool verifyRefModifiers(ImmutableArray modifiers, RefKind refKind, bool allowOut) + { + Debug.Assert(RefKind.RefReadOnly == RefKind.In); + switch (refKind) + { + case RefKind.RefReadOnly: + case RefKind.Out when allowOut: + return modifiers.Length == 1; + default: + return modifiers.IsEmpty; + } + } + } + private bool ArePropertiesEqual(PropertySymbol property, PropertySymbol other) { Debug.Assert(StringOrdinalComparer.Equals(property.MetadataName, other.MetadataName)); @@ -781,6 +866,9 @@ private bool AreTypesEqual(TypeSymbol type, TypeSymbol other) case SymbolKind.PointerType: return ArePointerTypesEqual((PointerTypeSymbol)type, (PointerTypeSymbol)other); + case SymbolKind.FunctionPointer: + return AreFunctionPointerTypesEqual((FunctionPointerTypeSymbol)type, (FunctionPointerTypeSymbol)other); + case SymbolKind.NamedType: case SymbolKind.ErrorType: return AreNamedTypesEqual((NamedTypeSymbol)type, (NamedTypeSymbol)other); @@ -915,6 +1003,35 @@ public override Symbol VisitPointerType(PointerTypeSymbol symbol) return new PointerTypeSymbol(symbol.PointedAtTypeWithAnnotations.WithTypeAndModifiers(translatedPointedAtType, translatedModifiers)); } + public override Symbol VisitFunctionPointerType(FunctionPointerTypeSymbol symbol) + { + var sig = symbol.Signature; + var translatedReturnType = (TypeSymbol)Visit(sig.ReturnType); + var translatedReturnTypeWithAnnotations = sig.ReturnTypeWithAnnotations.WithTypeAndModifiers(translatedReturnType, VisitCustomModifiers(sig.ReturnTypeWithAnnotations.CustomModifiers)); + var translatedRefCustomModifiers = VisitCustomModifiers(sig.RefCustomModifiers); + + var translatedParameterTypes = ImmutableArray.Empty; + ImmutableArray> translatedParamRefCustomModifiers = default; + + if (sig.ParameterCount > 0) + { + var translatedParamsBuilder = ArrayBuilder.GetInstance(sig.ParameterCount); + var translatedParamRefCustomModifiersBuilder = ArrayBuilder>.GetInstance(sig.ParameterCount); + + foreach (var param in sig.Parameters) + { + var translatedParamType = (TypeSymbol)Visit(param.Type); + translatedParamsBuilder.Add(param.TypeWithAnnotations.WithTypeAndModifiers(translatedParamType, VisitCustomModifiers(param.TypeWithAnnotations.CustomModifiers))); + translatedParamRefCustomModifiersBuilder.Add(VisitCustomModifiers(param.RefCustomModifiers)); + } + + translatedParameterTypes = translatedParamsBuilder.ToImmutableAndFree(); + translatedParamRefCustomModifiers = translatedParamRefCustomModifiersBuilder.ToImmutableAndFree(); + } + + return symbol.SubstituteTypeSymbol(translatedReturnTypeWithAnnotations, translatedParameterTypes, translatedRefCustomModifiers, translatedParamRefCustomModifiers); + } + public override Symbol VisitTypeParameter(TypeParameterSymbol symbol) { return symbol; diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs index 4086200c04444..5b3965d72f865 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs @@ -1272,5 +1272,135 @@ event Action F { add { } remove { } } Assert.Same(e0, matcher.MapDefinition(e1)); Assert.Same(f0, matcher.MapDefinition(f1)); } + + [Fact] + public void FunctionPointerMembersTranslated() + { + var source = @" +unsafe class C +{ + delegate* f1; + delegate* f2; + delegate* f3; + delegate* f4; + delegate* f5; + delegate* f6; + delegate* f7; +} +"; + + var compilation0 = CreateCompilation(source, options: TestOptions.UnsafeDebugDll, parseOptions: TestOptions.RegularPreview); + var compilation1 = compilation0.WithSource(source); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + for (int i = 1; i <= 7; i++) + { + var f_0 = compilation0.GetMember($"C.f{i}"); + var f_1 = compilation1.GetMember($"C.f{i}"); + + Assert.Same(f_0, matcher.MapDefinition(f_1)); + } + } + + [Theory] + [InlineData("C", "void")] + [InlineData("C", "object")] + [InlineData("C", "ref C")] + [InlineData("C", "ref readonly C")] + [InlineData("ref C", "ref readonly C")] + public void FunctionPointerMembers_ReturnMismatch(string return1, string return2) + { + var source1 = $@" +unsafe class C +{{ + delegate* f1; +}}"; + + var source2 = $@" +unsafe class C +{{ + delegate* f1; +}}"; + + var compilation0 = CreateCompilation(source1, options: TestOptions.UnsafeDebugDll, parseOptions: TestOptions.RegularPreview); + var compilation1 = compilation0.WithSource(source2); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var f_1 = compilation1.GetMember($"C.f1"); + + Assert.Null(matcher.MapDefinition(f_1)); + } + + [Theory] + [InlineData("C", "object")] + [InlineData("C", "ref C")] + [InlineData("C", "out C")] + [InlineData("C", "in C")] + [InlineData("ref C", "out C")] + [InlineData("ref C", "in C")] + [InlineData("out C", "in C")] + [InlineData("C, C", "C")] + public void FunctionPointerMembers_ParamMismatch(string param1, string param2) + { + var source1 = $@" +unsafe class C +{{ + delegate*<{param1}, C, void>* f1; +}}"; + + var source2 = $@" +unsafe class C +{{ + delegate*<{param2}, C, void>* f1; +}}"; + + verify(source1, source2); + + source1 = $@" +unsafe class C +{{ + delegate* f1; +}}"; + + source2 = $@" +unsafe class C +{{ + delegate* f1; +}}"; + + verify(source1, source2); + + static void verify(string source1, string source2) + { + var compilation0 = CreateCompilation(source1, options: TestOptions.UnsafeDebugDll, parseOptions: TestOptions.RegularPreview); + var compilation1 = compilation0.WithSource(source2); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var f_1 = compilation1.GetMember($"C.f1"); + + Assert.Null(matcher.MapDefinition(f_1)); + } + } } }