From 51de45f98f5cee6677e017e0e6ef27aabe92999d Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Thu, 7 Aug 2025 17:37:22 -0700 Subject: [PATCH 1/4] EnC: Fix symbol mapping of delegates with indexed name --- .../EditAndContinue/CSharpSymbolMatcher.cs | 97 ++-- .../Emitter/EditAndContinue/EmitHelpers.cs | 45 +- .../EditAndContinue/PEDeltaAssemblyBuilder.cs | 23 - .../Portable/Emitter/Model/PEModuleBuilder.cs | 10 - .../AnonymousTypeManager.Templates.cs | 137 ++--- .../AnonymousTypes/AnonymousTypeManager.cs | 1 + .../EditAndContinueClosureTests.cs | 60 +++ .../EditAndContinue/EditAndContinueTests.cs | 478 +++++++++++++++--- .../EditAndContinue/SymbolMatcherTests.cs | 23 +- .../Portable/Emit/CommonPEModuleBuilder.cs | 13 + .../EditAndContinue/DeltaMetadataWriter.cs | 5 +- .../IPEDeltaAssemblyBuilder.cs | 1 - .../Emit/EditAndContinue/SymbolMatcher.cs | 96 +++- .../EditAndContinue/SynthesizedTypeMaps.cs | 10 +- .../CommonAnonymousTypeManager.cs | 4 + .../Core/Compilation/CompilationDifference.cs | 5 + .../Test/Core/Metadata/ILValidation.cs | 2 + .../Emit/EditAndContinue/EmitHelpers.vb | 47 +- .../EditAndContinue/PEDeltaAssemblyBuilder.vb | 17 - .../VisualBasicSymbolMatcher.vb | 48 +- .../Portable/Emit/PEModuleBuilder.vb | 4 - .../AnonymousTypeManager_Templates.vb | 59 +-- .../EditAndContinueTestBase.vb | 12 +- .../EditAndContinue/EditAndContinueTests.vb | 297 ++++++++--- .../EditAndContinue/SymbolMatcherTests.vb | 12 +- .../EditAndContinue/StatementEditingTests.cs | 42 +- .../AbstractEditAndContinueAnalyzer.cs | 19 +- .../EditAndContinueTest.GenerationVerifier.cs | 20 +- 28 files changed, 1090 insertions(+), 497 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs index c09438b4b510a..73f2587e9ff47 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs @@ -7,13 +7,12 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.CodeGen; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.Emit; -using Microsoft.CodeAnalysis.Emit.EditAndContinue; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; @@ -27,25 +26,31 @@ internal sealed class CSharpSymbolMatcher : SymbolMatcher public CSharpSymbolMatcher( SourceAssemblySymbol sourceAssembly, SourceAssemblySymbol otherAssembly, - SynthesizedTypeMaps synthesizedTypes, - IReadOnlyDictionary>? otherSynthesizedMembers, - IReadOnlyDictionary>? otherDeletedMembers) + SynthesizedTypeMaps otherSynthesizedTypes, + IReadOnlyDictionary> otherSynthesizedMembers, + IReadOnlyDictionary> otherDeletedMembers) { - _visitor = new Visitor(sourceAssembly, otherAssembly, synthesizedTypes, otherSynthesizedMembers, otherDeletedMembers, new DeepTranslator(otherAssembly.GetSpecialType(SpecialType.System_Object))); + _visitor = new Visitor( + sourceAssembly, + otherAssembly, + otherSynthesizedTypes, + otherSynthesizedMembers, + otherDeletedMembers, + new DeepTranslator(otherAssembly.GetSpecialType(SpecialType.System_Object))); } public CSharpSymbolMatcher( - SynthesizedTypeMaps synthesizedTypes, SourceAssemblySymbol sourceAssembly, - PEAssemblySymbol otherAssembly) + PEAssemblySymbol otherAssembly, + SynthesizedTypeMaps otherSynthesizedTypes) { _visitor = new Visitor( sourceAssembly, otherAssembly, - synthesizedTypes, - otherSynthesizedMembers: null, - deepTranslator: null, - otherDeletedMembers: null); + otherSynthesizedTypes, + otherSynthesizedMembers: SpecializedCollections.EmptyReadOnlyDictionary>(), + otherDeletedMembers: SpecializedCollections.EmptyReadOnlyDictionary>(), + deepTranslator: null); } public override Cci.IDefinition? MapDefinition(Cci.IDefinition definition) @@ -93,9 +98,12 @@ static bool isPrivateImplementationDetail(Cci.IDefinition definition) internal bool TryGetAnonymousTypeValue(AnonymousTypeManager.AnonymousTypeOrDelegateTemplateSymbol template, out AnonymousTypeValue typeValue) => _visitor.TryGetAnonymousTypeValue(template, out typeValue); + protected override bool TryGetMatchingDelegateWithIndexedName(INamedTypeSymbolInternal delegateTemplate, ImmutableArray values, out AnonymousTypeValue match) + => _visitor.TryGetMatchingDelegateWithIndexedName((AnonymousTypeManager.AnonymousDelegateTemplateSymbol)delegateTemplate, values, out match); + private sealed class Visitor : CSharpSymbolVisitor { - private readonly SynthesizedTypeMaps _synthesizedTypes; + private readonly SynthesizedTypeMaps _otherSynthesizedTypes; private readonly SourceAssemblySymbol _sourceAssembly; // metadata or source assembly: @@ -105,9 +113,9 @@ private sealed class Visitor : CSharpSymbolVisitor /// Members that are not listed directly on their containing type or namespace symbol as they were synthesized in a lowering phase, /// after the symbol has been created. /// - private readonly IReadOnlyDictionary>? _otherSynthesizedMembers; + private readonly IReadOnlyDictionary> _otherSynthesizedMembers; - private readonly IReadOnlyDictionary>? _otherDeletedMembers; + private readonly IReadOnlyDictionary> _otherDeletedMembers; private readonly SymbolComparer _comparer; private readonly ConcurrentDictionary _matches = new(ReferenceEqualityComparer.Instance); @@ -123,12 +131,12 @@ private sealed class Visitor : CSharpSymbolVisitor public Visitor( SourceAssemblySymbol sourceAssembly, AssemblySymbol otherAssembly, - SynthesizedTypeMaps synthesizedTypes, - IReadOnlyDictionary>? otherSynthesizedMembers, - IReadOnlyDictionary>? otherDeletedMembers, + SynthesizedTypeMaps otherSynthesizedTypes, + IReadOnlyDictionary> otherSynthesizedMembers, + IReadOnlyDictionary> otherDeletedMembers, DeepTranslator? deepTranslator) { - _synthesizedTypes = synthesizedTypes; + _otherSynthesizedTypes = otherSynthesizedTypes; _sourceAssembly = sourceAssembly; _otherAssembly = otherAssembly; _otherSynthesizedMembers = otherSynthesizedMembers; @@ -470,45 +478,44 @@ private CustomModifier VisitCustomModifier(CustomModifier modifier) CSharpCustomModifier.CreateRequired(type); } - internal bool TryGetAnonymousDelegateValue(AnonymousTypeManager.AnonymousDelegateTemplateSymbol delegateSymbol, out SynthesizedDelegateValue otherDelegateSymbol) + private bool TryGetAnonymousDelegateValue(AnonymousTypeManager.AnonymousDelegateTemplateSymbol delegateSymbol, out SynthesizedDelegateValue otherDelegateSymbol) { - Debug.Assert((object)delegateSymbol.ContainingSymbol == (object)_sourceAssembly.GlobalNamespace); - var key = new SynthesizedDelegateKey(delegateSymbol.MetadataName); - return _synthesizedTypes.AnonymousDelegates.TryGetValue(key, out otherDelegateSymbol); + return _otherSynthesizedTypes.AnonymousDelegates.TryGetValue(key, out otherDelegateSymbol); } internal bool TryGetAnonymousTypeValue(AnonymousTypeManager.AnonymousTypeOrDelegateTemplateSymbol template, out AnonymousTypeValue otherType) { - Debug.Assert((object)template.ContainingSymbol == (object)_sourceAssembly.GlobalNamespace); - if (template is AnonymousTypeManager.AnonymousTypeTemplateSymbol typeTemplate) { - return _synthesizedTypes.AnonymousTypes.TryGetValue(typeTemplate.GetAnonymousTypeKey(), out otherType); + return _otherSynthesizedTypes.AnonymousTypes.TryGetValue(typeTemplate.GetAnonymousTypeKey(), out otherType); } var delegateTemplate = (AnonymousTypeManager.AnonymousDelegateTemplateSymbol)template; Debug.Assert(delegateTemplate.DelegateInvokeMethod != null); + otherType = default; + var key = new AnonymousDelegateWithIndexedNamePartialKey(delegateTemplate.Arity, delegateTemplate.DelegateInvokeMethod.ParameterCount); - if (_synthesizedTypes.AnonymousDelegatesWithIndexedNames.TryGetValue(key, out var otherTypeCandidates)) + return _otherSynthesizedTypes.AnonymousDelegatesWithIndexedNames.TryGetValue(key, out var otherTypeCandidates) && + TryGetMatchingDelegateWithIndexedName(delegateTemplate, otherTypeCandidates, out otherType); + } + + internal bool TryGetMatchingDelegateWithIndexedName(AnonymousTypeManager.AnonymousDelegateTemplateSymbol delegateTemplate, ImmutableArray values, out AnonymousTypeValue match) + { + foreach (var otherTypeCandidate in values) { - // The key is partial (not unique). Find a matching Invoke method signature. + var otherDelegateType = (NamedTypeSymbol?)otherTypeCandidate.Type.GetInternalSymbol(); + Debug.Assert(otherDelegateType is not null); - foreach (var otherTypeCandidate in otherTypeCandidates) + if (isCorrespondingAnonymousDelegate(delegateTemplate, otherDelegateType)) { - var otherDelegateType = (NamedTypeSymbol?)otherTypeCandidate.Type.GetInternalSymbol(); - Debug.Assert(otherDelegateType is not null); - - if (isCorrespondingAnonymousDelegate(delegateTemplate, otherDelegateType)) - { - otherType = otherTypeCandidate; - return true; - } + match = otherTypeCandidate; + return true; } } - otherType = default; + match = default; return false; bool isCorrespondingAnonymousDelegate(NamedTypeSymbol type, NamedTypeSymbol otherType) @@ -526,12 +533,12 @@ otherType.DelegateInvokeMethod is { } otherInvokeMethod && x.IsParamsArray == y.IsParamsArray && x.IsParamsCollection == y.IsParamsCollection) && isCorrespondingType(invokeMethod.ReturnTypeWithAnnotations, otherInvokeMethod.ReturnTypeWithAnnotations); - } - bool isCorrespondingType(TypeWithAnnotations type, TypeWithAnnotations expectedType) - { - var otherType = type.WithTypeAndModifiers((TypeSymbol?)this.Visit(type.Type), this.VisitCustomModifiers(type.CustomModifiers)); - return otherType.Equals(expectedType, TypeCompareKind.CLRSignatureCompareOptions); + bool isCorrespondingType(TypeWithAnnotations type, TypeWithAnnotations expectedType) + { + var otherType = type.WithTypeAndModifiers((TypeSymbol?)this.Visit(type.Type), this.VisitCustomModifiers(type.CustomModifiers)); + return otherType.Equals(expectedType, TypeCompareKind.CLRSignatureCompareOptions); + } } } @@ -799,12 +806,12 @@ private IReadOnlyDictionary> GetAllEmitt members.AddRange(((NamespaceSymbol)symbol).GetMembers()); } - if (_otherSynthesizedMembers != null && _otherSynthesizedMembers.TryGetValue(symbol, out var synthesizedMembers)) + if (_otherSynthesizedMembers.TryGetValue(symbol, out var synthesizedMembers)) { members.AddRange(synthesizedMembers); } - if (_otherDeletedMembers?.TryGetValue(symbol, out var deletedMembers) == true) + if (_otherDeletedMembers.TryGetValue(symbol, out var deletedMembers)) { members.AddRange(deletedMembers); } diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs index 222963e9f5591..87ac714731ef1 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs @@ -67,14 +67,14 @@ internal static EmitDifferenceResult EmitDifference( var metadataAssembly = (PEAssemblySymbol)metadataDecoder.ModuleSymbol.ContainingAssembly; var sourceToMetadata = new CSharpSymbolMatcher( - metadataSymbols.SynthesizedTypes, - sourceAssembly, - metadataAssembly); + sourceAssembly: sourceAssembly, + otherAssembly: metadataAssembly, + otherSynthesizedTypes: metadataSymbols.SynthesizedTypes); var previousSourceToMetadata = new CSharpSymbolMatcher( - metadataSymbols.SynthesizedTypes, - previousSourceAssembly, - metadataAssembly); + sourceAssembly: previousSourceAssembly, + otherAssembly: metadataAssembly, + otherSynthesizedTypes: metadataSymbols.SynthesizedTypes); CSharpSymbolMatcher? currentSourceToPreviousSource = null; if (baseline.Ordinal > 0) @@ -84,7 +84,7 @@ internal static EmitDifferenceResult EmitDifference( currentSourceToPreviousSource = new CSharpSymbolMatcher( sourceAssembly: sourceAssembly, otherAssembly: previousSourceAssembly, - baseline.SynthesizedTypes, + otherSynthesizedTypes: baseline.SynthesizedTypes, otherSynthesizedMembers: baseline.SynthesizedMembers, otherDeletedMembers: baseline.DeletedMembers); } @@ -205,7 +205,7 @@ internal static EmitBaseline MapToCompilation( RoslynDebug.AssertNotNull(previousGeneration.PEModuleBuilder); RoslynDebug.AssertNotNull(moduleBeingBuilt.EncSymbolChanges); - var synthesizedTypes = moduleBeingBuilt.GetSynthesizedTypes(); + var currentSynthesizedTypes = moduleBeingBuilt.GetAllSynthesizedTypes(); var currentSynthesizedMembers = moduleBeingBuilt.GetAllSynthesizedMembers(); var currentDeletedMembers = moduleBeingBuilt.EncSymbolChanges.DeletedMembers; @@ -213,11 +213,13 @@ internal static EmitBaseline MapToCompilation( var previousSourceAssembly = ((CSharpCompilation)previousGeneration.Compilation).SourceAssembly; var matcher = new CSharpSymbolMatcher( - previousSourceAssembly, - compilation.SourceAssembly, - synthesizedTypes, - currentSynthesizedMembers, - currentDeletedMembers); + sourceAssembly: previousSourceAssembly, + otherAssembly: compilation.SourceAssembly, + otherSynthesizedTypes: currentSynthesizedTypes, + otherSynthesizedMembers: currentSynthesizedMembers, + otherDeletedMembers: currentDeletedMembers); + + var mappedSynthesizedTypes = matcher.MapSynthesizedTypes(previousGeneration.SynthesizedTypes, currentSynthesizedTypes); var mappedSynthesizedMembers = matcher.MapSynthesizedOrDeletedMembers(previousGeneration.SynthesizedMembers, currentSynthesizedMembers, isDeletedMemberMapping: false); @@ -225,17 +227,18 @@ internal static EmitBaseline MapToCompilation( var mappedDeletedMembers = matcher.MapSynthesizedOrDeletedMembers(previousGeneration.DeletedMembers, currentDeletedMembers, isDeletedMemberMapping: true); // TODO: can we reuse some data from the previous matcher? - var matcherWithAllSynthesizedMembers = new CSharpSymbolMatcher( - previousSourceAssembly, - compilation.SourceAssembly, - synthesizedTypes, - mappedSynthesizedMembers, - mappedDeletedMembers); - - return matcherWithAllSynthesizedMembers.MapBaselineToCompilation( + var matcherWithAllSynthesizedTypesAndMembers = new CSharpSymbolMatcher( + sourceAssembly: previousSourceAssembly, + otherAssembly: compilation.SourceAssembly, + otherSynthesizedTypes: mappedSynthesizedTypes, + otherSynthesizedMembers: mappedSynthesizedMembers, + otherDeletedMembers: mappedDeletedMembers); + + return matcherWithAllSynthesizedTypesAndMembers.MapBaselineToCompilation( previousGeneration, compilation, moduleBeingBuilt, + mappedSynthesizedTypes, mappedSynthesizedMembers, mappedDeletedMembers); } diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs index 08e1d677504f8..29d9e47fd17ad 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs @@ -224,19 +224,6 @@ private static bool TryGetAnonymousTypeKey( internal CSharpDefinitionMap PreviousDefinitions => (CSharpDefinitionMap)_changes.DefinitionMap; - public SynthesizedTypeMaps GetSynthesizedTypes() - { - var result = new SynthesizedTypeMaps( - Compilation.AnonymousTypeManager.GetAnonymousTypeMap(), - Compilation.AnonymousTypeManager.GetAnonymousDelegates(), - Compilation.AnonymousTypeManager.GetAnonymousDelegatesWithIndexedNames()); - - // Should contain all entries in previous generation. - Debug.Assert(PreviousGeneration.SynthesizedTypes.IsSubsetOf(result)); - - return result; - } - public override IEnumerable GetTopLevelTypeDefinitions(EmitContext context) => GetTopLevelTypeDefinitionsExcludingNoPiaAndRootModule(context, includePrivateImplementationDetails: true); @@ -258,16 +245,6 @@ internal override MethodInstrumentation GetMethodBodyInstrumentations(MethodSymb return _changes.DefinitionMap.GetMethodBodyInstrumentations(method); } - internal override ImmutableArray GetPreviousAnonymousTypes() - { - return ImmutableArray.CreateRange(PreviousGeneration.SynthesizedTypes.AnonymousTypes.Keys); - } - - internal override ImmutableArray GetPreviousAnonymousDelegates() - { - return ImmutableArray.CreateRange(PreviousGeneration.SynthesizedTypes.AnonymousDelegates.Keys); - } - internal override int GetNextAnonymousTypeIndex() => PreviousGeneration.GetNextAnonymousTypeIndex(); diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs index 44178fe8e4c02..1497f5dad0859 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs @@ -505,16 +505,6 @@ internal virtual VariableSlotAllocator TryCreateVariableSlotAllocator(MethodSymb internal virtual MethodInstrumentation GetMethodBodyInstrumentations(MethodSymbol method) => new MethodInstrumentation { Kinds = EmitOptions.InstrumentationKinds }; - internal virtual ImmutableArray GetPreviousAnonymousTypes() - { - return ImmutableArray.Empty; - } - - internal virtual ImmutableArray GetPreviousAnonymousDelegates() - { - return ImmutableArray.Empty; - } - internal virtual int GetNextAnonymousTypeIndex() { return 0; diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs index 157718ec50f0e..9faba7e831c35 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -29,17 +27,17 @@ internal sealed partial class AnonymousTypeManager /// Cache of created anonymous type templates used as an implementation of anonymous /// types in emit phase. /// - private ConcurrentDictionary _lazyAnonymousTypeTemplates; + private ConcurrentDictionary? _lazyAnonymousTypeTemplates; /// /// Maps delegate signature shape (number of parameters and their ref-ness) to a synthesized generic delegate symbol. /// Currently used for dynamic call-sites and inferred delegate types whose signature doesn't match any of the well-known Func or Action types. /// - private ConcurrentDictionary _lazyAnonymousDelegates; + private ConcurrentDictionary? _lazyAnonymousDelegates; private readonly struct SynthesizedDelegateKey : IEquatable { - internal readonly string Name; + internal readonly string? Name; internal readonly int ParameterCount; internal readonly AnonymousTypeDescriptor TypeDescriptor; @@ -57,7 +55,7 @@ public SynthesizedDelegateKey(AnonymousTypeDescriptor typeDescr) TypeDescriptor = typeDescr; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is SynthesizedDelegateKey && Equals((SynthesizedDelegateKey)obj); } @@ -111,6 +109,7 @@ private void CheckSourceLocationSeen(AnonymousTypePublicSymbol anonymous) #endif } +#nullable enable private ConcurrentDictionary AnonymousTypeTemplates { get @@ -118,10 +117,8 @@ private ConcurrentDictionary AnonymousTypeT // Lazily create a template types cache if (_lazyAnonymousTypeTemplates == null) { - CSharpCompilation previousSubmission = this.Compilation.PreviousSubmission; - // TODO (tomat): avoid recursion - var previousCache = (previousSubmission == null) ? null : previousSubmission.AnonymousTypeManager.AnonymousTypeTemplates; + var previousCache = Compilation.PreviousSubmission?.AnonymousTypeManager.AnonymousTypeTemplates; Interlocked.CompareExchange(ref _lazyAnonymousTypeTemplates, previousCache == null @@ -140,10 +137,8 @@ private ConcurrentDictionary f.Type); return template.Construct(typeArguments); } -#nullable disable - - private AnonymousTypeTemplateSymbol CreatePlaceholderTemplate(Microsoft.CodeAnalysis.Emit.AnonymousTypeKey key) - { - var fields = key.Fields.SelectAsArray(f => new AnonymousTypeField(f.Name, Location.None, typeWithAnnotations: default, refKind: RefKind.None, ScopedKind.None)); - var typeDescr = new AnonymousTypeDescriptor(fields, Location.None); - return new AnonymousTypeTemplateSymbol(this, typeDescr); - } - - private AnonymousDelegateTemplateSymbol CreatePlaceholderSynthesizedDelegateValue(string name, RefKindVector refKinds, bool returnsVoid, int parameterCount) - { - return new AnonymousDelegateTemplateSymbol( - this, - MetadataHelpers.InferTypeArityAndUnmangleMetadataName(name, out _), - this.System_Object, - Compilation.GetSpecialType(SpecialType.System_IntPtr), - returnsVoid ? Compilation.GetSpecialType(SpecialType.System_Void) : null, - parameterCount, - refKinds); - } /// /// Resets numbering in anonymous type names and compiles the @@ -483,53 +458,26 @@ private AnonymousDelegateTemplateSymbol CreatePlaceholderSynthesizedDelegateValu /// public void AssignTemplatesNamesAndCompile(MethodCompiler compiler, PEModuleBuilder moduleBeingBuilt, BindingDiagnosticBag diagnostics) { - // Ensure all previous anonymous type templates are included so the - // types are available for subsequent edit and continue generations. - foreach (var key in moduleBeingBuilt.GetPreviousAnonymousTypes()) - { - Debug.Assert(!key.IsDelegate); - var templateKey = AnonymousTypeDescriptor.ComputeKey(key.Fields, f => f.Name); - this.AnonymousTypeTemplates.GetOrAdd(templateKey, k => this.CreatePlaceholderTemplate(key)); - } - // Get all anonymous types owned by this manager var anonymousTypes = ArrayBuilder.GetInstance(); + var anonymousDelegates = ArrayBuilder.GetInstance(); var anonymousDelegatesWithIndexedNames = ArrayBuilder.GetInstance(); + GetCreatedAnonymousTypeTemplates(anonymousTypes); + GetCreatedAnonymousDelegates(anonymousDelegates); GetCreatedAnonymousDelegatesWithIndexedNames(anonymousDelegatesWithIndexedNames); // If the collection is not sealed yet we should assign // new indexes to the created anonymous type templates - if (!this.AreTemplatesSealed) + if (!AreTemplatesSealed) { - // If we are emitting .NET module, include module's name into type's name to ensure - // uniqueness across added modules. - string moduleId; - - if (moduleBeingBuilt.OutputKind == OutputKind.NetModule) - { - moduleId = moduleBeingBuilt.Name; - - string extension = OutputKind.NetModule.GetDefaultExtension(); - - if (moduleId.EndsWith(extension, StringComparison.OrdinalIgnoreCase)) - { - moduleId = moduleId.Substring(0, moduleId.Length - extension.Length); - } - - moduleId = MetadataHelpers.MangleForTypeNameIfNeeded(moduleId); - } - else - { - moduleId = string.Empty; - } - - int submissionSlotIndex = this.Compilation.GetSubmissionSlotIndex(); + string moduleId = getModuleId(); + int submissionSlotIndex = Compilation.GetSubmissionSlotIndex(); - assignNames(anonymousTypes, moduleBeingBuilt.GetNextAnonymousTypeIndex(), isDelegate: false); - assignNames(anonymousDelegatesWithIndexedNames, moduleBeingBuilt.GetNextAnonymousDelegateIndex(), isDelegate: true); + assignIndexedNames(anonymousTypes, moduleBeingBuilt.GetNextAnonymousTypeIndex(), isDelegate: false); + assignIndexedNames(anonymousDelegatesWithIndexedNames, moduleBeingBuilt.GetNextAnonymousDelegateIndex(), isDelegate: true); - void assignNames(IReadOnlyList templates, int nextIndex, bool isDelegate) + void assignIndexedNames(IReadOnlyList templates, int nextIndex, bool isDelegate) { foreach (var template in templates) { @@ -550,7 +498,7 @@ void assignNames(IReadOnlyList templates, } } - this.SealTemplates(); + SealTemplates(); } if (anonymousTypes.Count > 0 && !ReportMissingOrErroneousSymbols(diagnostics)) @@ -566,21 +514,6 @@ void assignNames(IReadOnlyList templates, } } - anonymousTypes.Free(); - - // Ensure all previous synthesized delegates are included so the - // types are available for subsequent edit and continue generations. - foreach (var key in moduleBeingBuilt.GetPreviousAnonymousDelegates()) - { - if (GeneratedNames.TryParseSynthesizedDelegateName(key.Name, out var refKinds, out var returnsVoid, out var generation, out var parameterCount)) - { - var delegateKey = new SynthesizedDelegateKey(parameterCount, refKinds, returnsVoid, generation); - this.AnonymousDelegates.GetOrAdd(delegateKey, (k, args) => CreatePlaceholderSynthesizedDelegateValue(key.Name, args.refKinds, args.returnsVoid, args.parameterCount), (refKinds, returnsVoid, parameterCount)); - } - } - - var anonymousDelegates = ArrayBuilder.GetInstance(); - GetCreatedAnonymousDelegates(anonymousDelegates); if (anonymousDelegatesWithIndexedNames.Count > 0 || anonymousDelegates.Count > 0) { ReportMissingOrErroneousSymbolsForDelegates(diagnostics); @@ -593,9 +526,33 @@ void assignNames(IReadOnlyList templates, compiler.Visit(anonymousDelegate, null); } } - anonymousDelegates.Free(); + anonymousTypes.Free(); + anonymousDelegates.Free(); anonymousDelegatesWithIndexedNames.Free(); + + string getModuleId() + { + // If we are emitting .NET module, include module's name into type's name to ensure + // uniqueness across added modules. + + if (moduleBeingBuilt.OutputKind == OutputKind.NetModule) + { + string extension = OutputKind.NetModule.GetDefaultExtension(); + + var moduleId = moduleBeingBuilt.Name; + if (moduleId.EndsWith(extension, StringComparison.OrdinalIgnoreCase)) + { + moduleId = moduleId[..^extension.Length]; + } + + return MetadataHelpers.MangleForTypeNameIfNeeded(moduleId); + } + else + { + return string.Empty; + } + } } /// @@ -658,7 +615,7 @@ private void GetCreatedAnonymousDelegates(ArrayBuilder { public static readonly SynthesizedDelegateSymbolComparer Instance = new SynthesizedDelegateSymbolComparer(); @@ -741,6 +698,12 @@ internal ImmutableArray GetAllCreatedTemplates() return builder.ToImmutableAndFree(); } + internal override SynthesizedTypeMaps GetSynthesizedTypeMaps() + => new SynthesizedTypeMaps( + GetAnonymousTypeMap(), + GetAnonymousDelegates(), + GetAnonymousDelegatesWithIndexedNames()); + /// /// Returns true if the named type is an implementation template for an anonymous type /// diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs index 913808058a9d8..7614e068639e9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Symbols; namespace Microsoft.CodeAnalysis.CSharp.Symbols diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueClosureTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueClosureTests.cs index 4b5b85deef2b2..2a06fe5f7c8b3 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueClosureTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueClosureTests.cs @@ -10071,6 +10071,7 @@ public void F() }) .AddGeneration( + // 1 source: """ using System; class C @@ -10141,6 +10142,65 @@ .maxstack 8 } """); }) + .AddGeneration( + // 2 + source: """ + using System; + class C + { + public void F() + { + _ = new Func(() => (byte)1); + } + } + """, + edits: + [ + Edit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true, rudeEdits: _ => new RuntimeRudeEdit("Return type changed", 0x123)), + ], + validator: g => + { + g.VerifySynthesizedMembers( + "System.Runtime.CompilerServices.HotReloadException", + "C: {<>c}", + "C.<>c: {<>9__0_0#2, b__0_0#2, <>9__0_0#1, b__0_0#1}"); + + g.VerifyMethodDefNames( + "F", "b__0_0#1", "b__0_0#2"); + + g.VerifyIL( + """ + F + { + // Code size 30 (0x1e) + .maxstack 8 + IL_0000: nop + IL_0001: ldsfld 0x04000005 + IL_0006: brtrue.s IL_001d + IL_0008: ldsfld 0x04000001 + IL_000d: ldftn 0x06000008 + IL_0013: newobj 0x0A00000B + IL_0018: stsfld 0x04000005 + IL_001d: ret + } + b__0_0#1 + { + // Code size 16 (0x10) + .maxstack 8 + IL_0000: ldstr 0x7000010D + IL_0005: ldc.i4 0x123 + IL_000a: newobj 0x06000006 + IL_000f: throw + } + b__0_0#2 + { + // Code size 2 (0x2) + .maxstack 8 + IL_0000: ldc.i4.1 + IL_0001: ret + } + """); + }) .Verify(); } } diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueTests.cs index 52fb9f8a63973..63462e1ba2182 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueTests.cs @@ -2256,6 +2256,256 @@ void F() "C: {<>c}"); } + [Fact] + public void Lambda_SynthesizedDelegate() + { + using var _ = new EditAndContinueTest() + .AddBaseline( + source: """ + class C + { + void F() + { + var f = (ref int a) => a; + } + } + """, + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "class C: {<>c}", + "class C.<>c: {<>9__0_0, b__0_0}" + ]); + + g.VerifySynthesizedTypes( + "<>F{00000001}"); + }) + .AddGeneration( + // 1 + source: """ + class C + { + void F() + { + var g = (out byte a) => a = 1; + var f = (ref int a) => a; + } + } + """, + edits: + [ + Edit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true), + ], + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "class C: {<>c}", + "class C.<>c: {<>9__0_0#1, <>9__0_0, b__0_0#1, b__0_0}" // new and reused lambdas + ]); + + g.VerifySynthesizedTypes( + "<>F{00000001}", + "<>F{00000002}"); // new synthesized delegate is created + + g.VerifyTypeDefNames("<>F{00000002}`2"); + g.VerifyMethodDefNames("F", "b__0_0", ".ctor", "Invoke", "b__0_0#1"); + }) + .AddGeneration( + // 2 + source: """ + class C + { + void F() + { + var f = (ref bool a, ref bool b) => a; + } + } + """, + edits: + [ + Edit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true, rudeEdits: _ => new RuntimeRudeEdit("Parameter changed", 0x123)), + ], + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "System.Runtime.CompilerServices.HotReloadException", + "class C: {<>c}", + "class C.<>c: {<>9__0_0#2, b__0_0#2, <>9__0_0#1, <>9__0_0, b__0_0#1, b__0_0}" + ]); + + g.VerifySynthesizedTypes( + "<>F{00000001}", + "<>F{00000002}", + "<>F{00000009}"); // new synthesized delegate is created + + g.VerifyTypeDefNames("<>F{00000009}`3", "HotReloadException"); + g.VerifyMethodDefNames("F", "b__0_0", "b__0_0#1", ".ctor", "Invoke", ".ctor", "b__0_0#2"); + }) + .AddGeneration( + // 3 + source: """ + class C + { + void F() + { + var f = (ref bool a, ref bool b) => a; + var g = (ref bool a, ref bool b, bool c) => a; + } + } + """, + edits: + [ + Edit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true), + ], + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "System.Runtime.CompilerServices.HotReloadException", + "class C: {<>c}", + "class C.<>c: {<>9__0_0#2, <>9__0_1#3, b__0_0#2, b__0_1#3, <>9__0_0#1, <>9__0_0, b__0_0#1, b__0_0}" + ]); + + g.VerifySynthesizedTypes( + "<>F{00000001}", + "<>F{00000002}", + "<>F{00000009}", + "<>F{00000009}"); // new synthesized delegate is created + + g.VerifyTypeDefNames("<>F{00000009}`4"); + g.VerifyMethodDefNames("F", "b__0_0#2", ".ctor", "Invoke", "b__0_1#3"); + }) + .Verify(); + } + + [Fact] + public void Lambda_SynthesizedDelegate_WithIndexedName() + { + using var _ = new EditAndContinueTest() + .AddBaseline( + source: """ + class C + { + void F() + { + var f = (int a = 1) => a; + } + } + """, + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "class C: {<>c}", + "class C.<>c: {<>9__0_0, b__0_0}" + ]); + + g.VerifySynthesizedTypes( + "<>f__AnonymousDelegate0"); + }) + .AddGeneration( + // 1 + source: """ + class C + { + void F() + { + var g = (int a = 1) => a + 1; + var f = (int a = 2) => a; + } + } + """, + edits: + [ + Edit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true), + ], + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "class C: {<>c}", + "class C.<>c: {<>9__0_0#1, <>9__0_0, b__0_0#1, b__0_0}" // new lambda is created + ]); + + g.VerifySynthesizedTypes( + "<>f__AnonymousDelegate0", + "<>f__AnonymousDelegate1"); // new synthesized delegate is created + + g.VerifyTypeDefNames("<>f__AnonymousDelegate1`2"); + g.VerifyMethodDefNames("F", "b__0_0", ".ctor", "Invoke", "b__0_0#1"); + }) + .AddGeneration( + // 2 + source: """ + class C + { + void F() + { + var f = (int a = 3) => a; + } + } + """, + edits: + [ + Edit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true, rudeEdits: _ => new RuntimeRudeEdit("Parameter changed", 0x123)), + ], + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "System.Runtime.CompilerServices.HotReloadException", + "class C: {<>c}", + "class C.<>c: {<>9__0_0#2, b__0_0#2, <>9__0_0#1, <>9__0_0, b__0_0#1, b__0_0}" + ]); + + g.VerifySynthesizedTypes( + "<>f__AnonymousDelegate0", + "<>f__AnonymousDelegate1", + "<>f__AnonymousDelegate2"); // new synthesized delegate is created + + g.VerifyTypeDefNames("<>f__AnonymousDelegate2`2", "HotReloadException"); + g.VerifyMethodDefNames("F", "b__0_0", "b__0_0#1", ".ctor", "Invoke", ".ctor", "b__0_0#2"); + }) + .AddGeneration( + // 3 + source: """ + class C + { + void F() + { + var f = (int a = 3) => a; + var g = (int a = 1, int b = 2) => a; + } + } + """, + edits: + [ + Edit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true), + ], + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "System.Runtime.CompilerServices.HotReloadException", + "class C: {<>c}", + "class C.<>c: {<>9__0_0#2, <>9__0_1#3, b__0_0#2, b__0_1#3, <>9__0_0#1, <>9__0_0, b__0_0#1, b__0_0}" + ]); + + g.VerifySynthesizedTypes( + "<>f__AnonymousDelegate0", + "<>f__AnonymousDelegate1", + "<>f__AnonymousDelegate2", + "<>f__AnonymousDelegate3"); // new synthesized delegate is created + + g.VerifyTypeDefNames("<>f__AnonymousDelegate3`3"); + g.VerifyMethodDefNames("F", "b__0_0#2", ".ctor", "Invoke", "b__0_1#3"); + }) + .Verify(); + } + [Fact] public void Lambda_Delete() { @@ -9871,94 +10121,162 @@ .locals init (<>f__AnonymousType0 V_0) //x /// /// Reuse existing anonymous types. /// - [WorkItem(825903, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/825903")] [Fact] + [WorkItem(825903, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/825903")] public void AnonymousTypes() { - var source0 = -@"namespace N -{ - class A - { - static object F = new { A = 1, B = 2 }; - } -} -namespace M -{ - class B - { - static void M() - { - var x = new { B = 3, A = 4 }; - var y = x.A; - var z = new { }; - } - } -}"; - var source1 = -@"namespace N -{ - class A - { - static object F = new { A = 1, B = 2 }; - } -} -namespace M -{ - class B - { - static void M() - { - var x = new { B = 3, A = 4 }; - var y = new { A = x.A }; - var z = new { }; - } - } -}"; - var compilation0 = CreateCompilation(source0, parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute(), options: TestOptions.DebugDll); - var compilation1 = compilation0.WithSource(source1); - - var m0 = compilation0.GetMember("M.B.M"); - var m1 = compilation1.GetMember("M.B.M"); - - var testData0 = new CompilationTestData(); - var bytes0 = compilation0.EmitToArray(testData: testData0); + using var _ = new EditAndContinueTest() + .AddBaseline( + source: """ + class C + { + void F() + { + var f = new { a = 1, b = 2 }; + } + } + """, + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "class <>f__AnonymousType0<j__TPar, j__TPar>: {Equals, GetHashCode, ToString}" + ]); - using var md0 = ModuleMetadata.CreateFromImage(bytes0); - var generation0 = CreateInitialBaseline(compilation0, md0, testData0.GetMethodData("M.B.M").EncDebugInfoProvider()); + g.VerifySynthesizedTypes( + "<>f__AnonymousType0<j__TPar, j__TPar>"); - var reader0 = md0.MetadataReader; - CheckNames(reader0, reader0.GetTypeDefNames(), "", "<>f__AnonymousType0`2", "<>f__AnonymousType1`2", "<>f__AnonymousType2", "B", "A"); + g.VerifyIL("C.F", """ + { + // Code size 10 (0xa) + .maxstack 2 + .locals init (<>f__AnonymousType0 V_0) //f + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: ldc.i4.2 + IL_0003: newobj "<>f__AnonymousType0..ctor(int, int)" + IL_0008: stloc.0 + IL_0009: ret + } + """); + }) + .AddGeneration( + // 1 + source: """ + class C + { + void F() + { + var g = new { x = 1 }; + var f = new { a = 1, b = 2 }; + } + } + """, + edits: + [ + Edit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true), + ], + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "class <>f__AnonymousType0<j__TPar, j__TPar>: {Equals, GetHashCode, ToString}", + "class <>f__AnonymousType1<j__TPar>: {Equals, GetHashCode, ToString}" + ]); - var diff1 = compilation1.EmitDifference( - generation0, - ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, m0, m1, GetEquivalentNodesMap(m1, m0)))); + g.VerifySynthesizedTypes( + "<>f__AnonymousType0<j__TPar, j__TPar>", + "<>f__AnonymousType1<j__TPar>"); - using var md1 = diff1.GetMetadata(); - var reader1 = md1.Reader; - CheckNames(new[] { reader0, reader1 }, reader1.GetTypeDefNames(), "<>f__AnonymousType3`1"); // one additional type + g.VerifyTypeDefNames("<>f__AnonymousType1`1"); + g.VerifyMethodDefNames("F", "get_x", ".ctor", "Equals", "GetHashCode", "ToString"); - diff1.VerifyIL("M.B.M", @" -{ - // Code size 28 (0x1c) - .maxstack 2 - .locals init (<>f__AnonymousType1 V_0, //x - [int] V_1, - <>f__AnonymousType2 V_2, //z - <>f__AnonymousType3 V_3) //y - IL_0000: nop - IL_0001: ldc.i4.3 - IL_0002: ldc.i4.4 - IL_0003: newobj ""<>f__AnonymousType1..ctor(int, int)"" - IL_0008: stloc.0 - IL_0009: ldloc.0 - IL_000a: callvirt ""int <>f__AnonymousType1.A.get"" - IL_000f: newobj ""<>f__AnonymousType3..ctor(int)"" - IL_0014: stloc.3 - IL_0015: newobj ""<>f__AnonymousType2..ctor()"" - IL_001a: stloc.2 - IL_001b: ret -}"); + g.VerifyIL("C.F", """ + { + // Code size 17 (0x11) + .maxstack 2 + .locals init (<>f__AnonymousType0 V_0, //f + <>f__AnonymousType1 V_1) //g + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: newobj "<>f__AnonymousType1..ctor(int)" + IL_0007: stloc.1 + IL_0008: ldc.i4.1 + IL_0009: ldc.i4.2 + IL_000a: newobj "<>f__AnonymousType0..ctor(int, int)" + IL_000f: stloc.0 + IL_0010: ret + } + """); + }) + .AddGeneration( + // 2 + source: """ + class C + { + void F() + { + var f = new { a = 1, b = 2, c = 3 }; + } + } + """, + edits: + [ + Edit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true), + ], + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "class <>f__AnonymousType0<j__TPar, j__TPar>: {Equals, GetHashCode, ToString}", + "class <>f__AnonymousType1<j__TPar>: {Equals, GetHashCode, ToString}", + "class <>f__AnonymousType2<j__TPar, j__TPar, j__TPar>: {Equals, GetHashCode, ToString}", + ]); + + g.VerifySynthesizedTypes( + "<>f__AnonymousType0<j__TPar, j__TPar>", + "<>f__AnonymousType1<j__TPar>", + "<>f__AnonymousType2<j__TPar, j__TPar, j__TPar>"); + + g.VerifyTypeDefNames("<>f__AnonymousType2`3"); + g.VerifyMethodDefNames("F", "get_a", "get_b", "get_c", ".ctor", "Equals", "GetHashCode", "ToString"); + }) + .AddGeneration( + // 3 + source: """ + class C + { + void F() + { + var f = new { a = 1, b = 2, c = 3 }; + var g = new { x = 1, y = 2 }; + } + } + """, + edits: + [ + Edit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true), + ], + validator: g => + { + g.VerifySynthesizedMembers(displayTypeKind: true, + [ + "class <>f__AnonymousType0<j__TPar, j__TPar>: {Equals, GetHashCode, ToString}", + "class <>f__AnonymousType1<j__TPar>: {Equals, GetHashCode, ToString}", + "class <>f__AnonymousType2<j__TPar, j__TPar, j__TPar>: {Equals, GetHashCode, ToString}", + "class <>f__AnonymousType3<j__TPar, j__TPar>: {Equals, GetHashCode, ToString}", + ]); + + g.VerifySynthesizedTypes( + "<>f__AnonymousType0<j__TPar, j__TPar>", + "<>f__AnonymousType1<j__TPar>", + "<>f__AnonymousType2<j__TPar, j__TPar, j__TPar>", + "<>f__AnonymousType3<j__TPar, j__TPar>"); + + g.VerifyTypeDefNames("<>f__AnonymousType3`2"); + g.VerifyMethodDefNames("F", "get_x", "get_y", ".ctor", "Equals", "GetHashCode", "ToString"); + }) + .Verify(); } /// diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/SymbolMatcherTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/SymbolMatcherTests.cs index 9561e0fe9c81b..12eb455d850fb 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/SymbolMatcherTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/SymbolMatcherTests.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Symbols; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -37,14 +38,14 @@ private static CSharpSymbolMatcher CreateMatcher(CSharpCompilation fromCompilati fromCompilation.SourceAssembly, toCompilation.SourceAssembly, SynthesizedTypeMaps.Empty, - otherSynthesizedMembers: null, - otherDeletedMembers: null); + otherSynthesizedMembers: SpecializedCollections.EmptyReadOnlyDictionary>(), + otherDeletedMembers: SpecializedCollections.EmptyReadOnlyDictionary>()); private static CSharpSymbolMatcher CreateMatcher(CSharpCompilation fromCompilation, PEAssemblySymbol peAssemblySymbol) => new CSharpSymbolMatcher( - SynthesizedTypeMaps.Empty, fromCompilation.SourceAssembly, - peAssemblySymbol); + peAssemblySymbol, + SynthesizedTypeMaps.Empty); private static IEnumerable Inspect(ImmutableSegmentedDictionary> anonymousDelegatesWithIndexedNames) => from entry in anonymousDelegatesWithIndexedNames @@ -505,7 +506,7 @@ static void F() Assert.Equal("x1", x1.Name); Assert.Equal("x2", x2.Name); - var matcher = new CSharpSymbolMatcher(synthesizedTypes0, compilation1.SourceAssembly, peAssemblySymbol0); + var matcher = new CSharpSymbolMatcher(compilation1.SourceAssembly, peAssemblySymbol0, synthesizedTypes0); var mappedX1 = (Cci.IFieldDefinition)matcher.MapDefinition(x1); var mappedX2 = (Cci.IFieldDefinition)matcher.MapDefinition(x2); @@ -575,7 +576,7 @@ static void F() var x1 = fields.Where(f => f.Name == "x1").Single(); var x2 = fields.Where(f => f.Name == "x2").Single(); - var matcher = new CSharpSymbolMatcher(synthesizedTypes0, compilation1.SourceAssembly, peAssemblySymbol0); + var matcher = new CSharpSymbolMatcher(compilation1.SourceAssembly, peAssemblySymbol0, synthesizedTypes0); var mappedX1 = (Cci.IFieldDefinition)matcher.MapDefinition(x1); var mappedX2 = (Cci.IFieldDefinition)matcher.MapDefinition(x2); @@ -1129,7 +1130,7 @@ static void M(string? x) var y1 = fields.Where(f => f.Name == "y1").Single(); var y2 = fields.Where(f => f.Name == "y2").Single(); - var matcher = new CSharpSymbolMatcher(synthesizedTypes0, compilation1.SourceAssembly, peAssemblySymbol0); + var matcher = new CSharpSymbolMatcher(compilation1.SourceAssembly, peAssemblySymbol0, synthesizedTypes0); var mappedY1 = (Cci.IFieldDefinition)matcher.MapDefinition(y1); var mappedY2 = (Cci.IFieldDefinition)matcher.MapDefinition(y2); @@ -1486,7 +1487,7 @@ static void F() Assert.Equal("<>9__0_1", field2.Name); Assert.Equal("<>9__0_2", field3.Name); - var matcher = new CSharpSymbolMatcher(synthesizedTypes0, compilation1.SourceAssembly, peAssemblySymbol0); + var matcher = new CSharpSymbolMatcher(compilation1.SourceAssembly, peAssemblySymbol0, synthesizedTypes0); var mappedField1 = (Cci.IFieldDefinition)matcher.MapDefinition(field1); var mappedField2 = (Cci.IFieldDefinition)matcher.MapDefinition(field2); @@ -1544,7 +1545,7 @@ static void Main() var field0 = displayClass.GetFields(emitContext).Single(f => f.Name == "<>9__0_0"); Assert.Equal("<>f__AnonymousDelegate0", field0.GetType(emitContext).ToString()); - var matcher = new CSharpSymbolMatcher(synthesizedTypes0, compilation1.SourceAssembly, peAssemblySymbol0); + var matcher = new CSharpSymbolMatcher(compilation1.SourceAssembly, peAssemblySymbol0, synthesizedTypes0); var field1 = (Cci.IFieldDefinition)matcher.MapDefinition(field0); Assert.Equal("<>9__0_0", field1.Name); } @@ -1614,7 +1615,7 @@ unsafe static void F() Assert.Equal("<>9__0_1", field2.Name); Assert.Equal("<>9__0_2", field3.Name); - var matcher = new CSharpSymbolMatcher(synthesizedTypes0, compilation1.SourceAssembly, peAssemblySymbol0); + var matcher = new CSharpSymbolMatcher(compilation1.SourceAssembly, peAssemblySymbol0, synthesizedTypes0); Assert.Null(matcher.MapDefinition(field1)); Assert.Null(matcher.MapDefinition(field2)); @@ -1684,7 +1685,7 @@ unsafe static void F() Assert.Equal("<>9__0_1", field2.Name); Assert.Equal("<>9__0_2", field3.Name); - var matcher = new CSharpSymbolMatcher(synthesizedTypes0, compilation1.SourceAssembly, peAssemblySymbol0); + var matcher = new CSharpSymbolMatcher(compilation1.SourceAssembly, peAssemblySymbol0, synthesizedTypes0); var mappedField1 = (Cci.IFieldDefinition)matcher.MapDefinition(field1); var mappedField2 = (Cci.IFieldDefinition)matcher.MapDefinition(field2); diff --git a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs index d693f2b79a6f9..77a45f03757e4 100644 --- a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs +++ b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs @@ -159,7 +159,17 @@ public void CreateDeletedMethodDefinitions(DiagnosticBag diagnosticBag) internal abstract IAssemblySymbolInternal CommonCorLibrary { get; } internal abstract CommonModuleCompilationState CommonModuleCompilationState { get; } internal abstract void CompilationFinished(); + + /// + /// Returns all type members synthesized when compiling method bodies for this module. + /// internal abstract ImmutableDictionary> GetAllSynthesizedMembers(); + + /// + /// Returns all delegates and anonymous templates synthesized when compiling method bodies for this module. + /// + internal abstract SynthesizedTypeMaps GetAllSynthesizedTypes(); + internal abstract CommonEmbeddedTypesManager CommonEmbeddedTypesManagerOpt { get; } internal abstract Cci.ITypeReference EncTranslateType(ITypeSymbolInternal type, DiagnosticBag diagnostics); public abstract IEnumerable GetSourceAssemblyAttributes(bool isRefAssembly); @@ -1077,6 +1087,9 @@ internal override ImmutableDictionary Compilation.CommonAnonymousTypeManager.GetSynthesizedTypeMaps(); + #endregion #region Token Mapping diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs index 96a218ede1dcf..6e54218dd9915 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs @@ -194,9 +194,10 @@ internal EmitBaseline GetDelta(Compilation compilation, Guid encId, MetadataSize tableSizes[i] = previousTableSizes[i] + deltaTableSizes[i]; } - // If the previous generation is 0 (metadata) get the synthesized members from the current compilation's builder, + // If the previous generation is 0 (metadata) get the synthesized members and types from the current compilation's builder, // otherwise members from the current compilation have already been merged into the baseline. var synthesizedMembers = (_previousGeneration.Ordinal == 0) ? module.GetAllSynthesizedMembers() : _previousGeneration.SynthesizedMembers; + var synthesizedTypes = (_previousGeneration.Ordinal == 0) ? module.GetAllSynthesizedTypes() : _previousGeneration.SynthesizedTypes; Debug.Assert(module.EncSymbolChanges is not null); var deletedMembers = (_previousGeneration.Ordinal == 0) ? module.EncSymbolChanges.DeletedMembers : _previousGeneration.DeletedMembers; @@ -238,7 +239,7 @@ internal EmitBaseline GetDelta(Compilation compilation, Guid encId, MetadataSize userStringStreamLengthAdded: metadataSizes.GetAlignedHeapSize(HeapIndex.UserString) + _previousGeneration.UserStringStreamLengthAdded, // Guid stream accumulates on the GUID heap unlike other heaps, so the previous generations are already included. guidStreamLengthAdded: metadataSizes.HeapSizes[(int)HeapIndex.Guid], - synthesizedTypes: ((IPEDeltaAssemblyBuilder)module).GetSynthesizedTypes(), + synthesizedTypes: synthesizedTypes, synthesizedMembers: synthesizedMembers, deletedMembers: deletedMembers, addedOrChangedMethods: AddRange(_previousGeneration.AddedOrChangedMethods, addedOrChangedMethodsByIndex), diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/IPEDeltaAssemblyBuilder.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/IPEDeltaAssemblyBuilder.cs index 968b922e4ebef..cf850e27e79f4 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/IPEDeltaAssemblyBuilder.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/IPEDeltaAssemblyBuilder.cs @@ -7,5 +7,4 @@ namespace Microsoft.CodeAnalysis.Emit; internal interface IPEDeltaAssemblyBuilder { void OnCreatedIndices(DiagnosticBag diagnostics); - SynthesizedTypeMaps GetSynthesizedTypes(); } diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs index c57e6b2d0d496..20e287c2e1069 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs @@ -8,7 +8,6 @@ using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Emit { @@ -17,6 +16,7 @@ internal abstract class SymbolMatcher public abstract Cci.ITypeReference? MapReference(Cci.ITypeReference reference); public abstract Cci.IDefinition? MapDefinition(Cci.IDefinition definition); public abstract Cci.INamespace? MapNamespace(Cci.INamespace @namespace); + protected abstract bool TryGetMatchingDelegateWithIndexedName(INamedTypeSymbolInternal delegateTemplate, ImmutableArray values, out AnonymousTypeValue match); public ISymbolInternal? MapDefinitionOrNamespace(ISymbolInternal symbol) { @@ -30,6 +30,7 @@ public EmitBaseline MapBaselineToCompilation( EmitBaseline baseline, Compilation targetCompilation, CommonPEModuleBuilder targetModuleBuilder, + SynthesizedTypeMaps mappedSynthesizedTypes, IReadOnlyDictionary> mappedSynthesizedMembers, IReadOnlyDictionary> mappedDeletedMembers) { @@ -62,10 +63,7 @@ public EmitBaseline MapBaselineToCompilation( stringStreamLengthAdded: baseline.StringStreamLengthAdded, userStringStreamLengthAdded: baseline.UserStringStreamLengthAdded, guidStreamLengthAdded: baseline.GuidStreamLengthAdded, - synthesizedTypes: new SynthesizedTypeMaps( - MapAnonymousTypes(baseline.SynthesizedTypes.AnonymousTypes), - MapAnonymousDelegates(baseline.SynthesizedTypes.AnonymousDelegates), - MapAnonymousDelegatesWithIndexedNames(baseline.SynthesizedTypes.AnonymousDelegatesWithIndexedNames)), + synthesizedTypes: mappedSynthesizedTypes, synthesizedMembers: mappedSynthesizedMembers, deletedMembers: mappedDeletedMembers, addedOrChangedMethods: MapAddedOrChangedMethods(baseline.AddedOrChangedMethods), @@ -105,48 +103,108 @@ private IReadOnlyDictionary MapAddedOrChangedMeth return result; } - private ImmutableSegmentedDictionary MapAnonymousTypes(IReadOnlyDictionary anonymousTypeMap) + private static ImmutableSegmentedDictionary MapAnonymousTypes( + ImmutableSegmentedDictionary previousTypes, + ImmutableSegmentedDictionary newTypes) { + if (previousTypes.Count == 0) + { + return newTypes; + } + var builder = ImmutableSegmentedDictionary.CreateBuilder(); + builder.AddRange(newTypes); - foreach (var (key, value) in anonymousTypeMap) + foreach (var (key, previousValue) in previousTypes) { - var type = (Cci.ITypeDefinition?)MapDefinition(value.Type); - Debug.Assert(type != null); - builder.Add(key, new AnonymousTypeValue(value.Name, value.UniqueIndex, type)); + if (newTypes.ContainsKey(key)) + { + continue; + } + + builder.Add(key, previousValue); } return builder.ToImmutable(); } - private ImmutableSegmentedDictionary MapAnonymousDelegates(IReadOnlyDictionary anonymousDelegates) + private static ImmutableSegmentedDictionary MapAnonymousDelegates( + ImmutableSegmentedDictionary previousDelegates, + ImmutableSegmentedDictionary newDelegates) { + if (previousDelegates.Count == 0) + { + return newDelegates; + } + var builder = ImmutableSegmentedDictionary.CreateBuilder(); + builder.AddRange(newDelegates); - foreach (var (key, value) in anonymousDelegates) + foreach (var (key, previousValue) in previousDelegates) { - var delegateTypeDef = (Cci.ITypeDefinition?)MapDefinition(value.Delegate); - Debug.Assert(delegateTypeDef != null); - builder.Add(key, new SynthesizedDelegateValue(delegateTypeDef)); + if (newDelegates.ContainsKey(key)) + { + continue; + } + + builder.Add(key, previousValue); } return builder.ToImmutable(); } private ImmutableSegmentedDictionary> MapAnonymousDelegatesWithIndexedNames( - IReadOnlyDictionary> anonymousDelegates) + ImmutableSegmentedDictionary> previousDelegates, + ImmutableSegmentedDictionary> newDelegates) { + if (previousDelegates.Count == 0) + { + return newDelegates; + } + var builder = ImmutableSegmentedDictionary.CreateBuilder>(); + builder.AddRange(newDelegates); - foreach (var (key, values) in anonymousDelegates) + foreach (var (key, previousValues) in previousDelegates) { - builder.Add(key, values.SelectAsArray(value => new AnonymousTypeValue( - value.Name, value.UniqueIndex, (Cci.ITypeDefinition?)MapDefinition(value.Type) ?? throw ExceptionUtilities.UnexpectedValue(value.Type)))); + if (!newDelegates.TryGetValue(key, out var newValues)) + { + builder.Add(key, previousValues); + continue; + } + + ArrayBuilder? mergedValuesBuilder = null; + + foreach (var previousValue in previousValues) + { + var template = (INamedTypeSymbolInternal?)previousValue.Type.GetInternalSymbol(); + Debug.Assert(template is not null); + + if (TryGetMatchingDelegateWithIndexedName(template, newValues, out _)) + { + continue; + } + + mergedValuesBuilder ??= ArrayBuilder.GetInstance(); + mergedValuesBuilder.Add(previousValue); + } + + if (mergedValuesBuilder != null) + { + mergedValuesBuilder.AddRange(newValues); + builder[key] = mergedValuesBuilder.ToImmutableAndFree(); + } } return builder.ToImmutable(); } + internal SynthesizedTypeMaps MapSynthesizedTypes(SynthesizedTypeMaps previousTypes, SynthesizedTypeMaps newTypes) + => new SynthesizedTypeMaps( + MapAnonymousTypes(previousTypes.AnonymousTypes, newTypes.AnonymousTypes), + MapAnonymousDelegates(previousTypes.AnonymousDelegates, newTypes.AnonymousDelegates), + MapAnonymousDelegatesWithIndexedNames(previousTypes.AnonymousDelegatesWithIndexedNames, newTypes.AnonymousDelegatesWithIndexedNames)); + /// /// Merges synthesized or deleted members generated during lowering, or emit, of the current compilation with aggregate /// synthesized or deleted members from all previous source generations (gen >= 1). diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/SynthesizedTypeMaps.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/SynthesizedTypeMaps.cs index 5b7555008a080..ecf353e7da635 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/SynthesizedTypeMaps.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/SynthesizedTypeMaps.cs @@ -31,14 +31,10 @@ public bool IsEmpty = anonymousDelegates ?? ImmutableSegmentedDictionary.Empty; /// - /// A map of the assembly identities of the baseline compilation to the identities of the original metadata AssemblyRefs. - /// Only includes identities that differ between these two. + /// In C#, the set of anonymous delegates with name that is not determined by parameter types only + /// and need to suffixed by an index (e.g. delegates may have same parameter types but differ in default parameter values); + /// in VB, this set is unused and empty. /// public ImmutableSegmentedDictionary> AnonymousDelegatesWithIndexedNames { get; } = anonymousDelegatesWithIndexedNames ?? ImmutableSegmentedDictionary>.Empty; - - public bool IsSubsetOf(SynthesizedTypeMaps other) - => AnonymousTypes.Keys.All(static (key, other) => other.AnonymousTypes.ContainsKey(key), other) && - AnonymousDelegates.Keys.All(static (key, other) => other.AnonymousDelegates.ContainsKey(key), other) && - AnonymousDelegatesWithIndexedNames.Keys.All(static (key, other) => other.AnonymousDelegatesWithIndexedNames.ContainsKey(key), other); } diff --git a/src/Compilers/Core/Portable/Symbols/AnonymousTypes/CommonAnonymousTypeManager.cs b/src/Compilers/Core/Portable/Symbols/AnonymousTypes/CommonAnonymousTypeManager.cs index f5076011f49ac..f85bde592e979 100644 --- a/src/Compilers/Core/Portable/Symbols/AnonymousTypes/CommonAnonymousTypeManager.cs +++ b/src/Compilers/Core/Portable/Symbols/AnonymousTypes/CommonAnonymousTypeManager.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.CodeAnalysis.Emit; + namespace Microsoft.CodeAnalysis.Symbols { internal abstract class CommonAnonymousTypeManager @@ -24,5 +26,7 @@ protected void SealTemplates() { _templatesSealed = ThreeState.True; } + + internal abstract SynthesizedTypeMaps GetSynthesizedTypeMaps(); } } diff --git a/src/Compilers/Test/Core/Compilation/CompilationDifference.cs b/src/Compilers/Test/Core/Compilation/CompilationDifference.cs index 164703d1fc3b4..4d7899ece7960 100644 --- a/src/Compilers/Test/Core/Compilation/CompilationDifference.cs +++ b/src/Compilers/Test/Core/Compilation/CompilationDifference.cs @@ -165,6 +165,11 @@ internal static void VerifySynthesizedMembers(IReadOnlyDictionary $"\"{s}\""); } + internal static void VerifySynthesizedSymbols(IEnumerable actualSymbols, params string[] expected) + { + AssertEx.SetEqual(expected, actualSymbols.Select(v => v.GetISymbol().ToDisplayString(SymbolDisplayFormat.TestFormat)), itemSeparator: ",\r\n", itemInspector: s => $"\"{s}\""); + } + public void VerifySynthesizedFields(string typeName, params string[] expectedSynthesizedTypesAndMemberCounts) { var actual = EmitResult.Baseline.SynthesizedMembers.Single(e => e.Key.ToString() == typeName).Value.Where(s => s.Kind == SymbolKind.Field).Select(s => (IFieldSymbol)s.GetISymbol()).Select(f => f.Name + ": " + f.Type); diff --git a/src/Compilers/Test/Core/Metadata/ILValidation.cs b/src/Compilers/Test/Core/Metadata/ILValidation.cs index 0d54449bcf116..a28f592bec5ef 100644 --- a/src/Compilers/Test/Core/Metadata/ILValidation.cs +++ b/src/Compilers/Test/Core/Metadata/ILValidation.cs @@ -271,6 +271,8 @@ public static unsafe string DumpEncDeltaMethodBodies(ImmutableArray il, Im var rvasAndNames = from handle in reader.MethodDefinitions let method = reader.GetMethodDefinition(handle) + // filter out runtime-implemented methods that do not have IL: + where (method.ImplAttributes & MethodImplAttributes.CodeTypeMask) == MethodImplAttributes.IL orderby method.RelativeVirtualAddress group method.Name by method.RelativeVirtualAddress into g // Legacy test support: name can only be resolved when readers for all generations are given. diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb index 3d7544b5909ed..ba74d2fc6ce5d 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb @@ -62,12 +62,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Dim metadataDecoder = DirectCast(metadataSymbols.MetadataDecoder, MetadataDecoder) Dim metadataAssembly = DirectCast(metadataDecoder.ModuleSymbol.ContainingAssembly, PEAssemblySymbol) - Dim matchToMetadata = New VisualBasicSymbolMatcher(initialBaseline.LazyMetadataSymbols.SynthesizedTypes, sourceAssembly, metadataAssembly) + Dim matchToMetadata = New VisualBasicSymbolMatcher( + sourceAssembly:=sourceAssembly, + otherAssembly:=metadataAssembly, + otherSynthesizedTypes:=initialBaseline.LazyMetadataSymbols.SynthesizedTypes) Dim previousSourceToMetadata = New VisualBasicSymbolMatcher( - metadataSymbols.SynthesizedTypes, - previousSourceAssembly, - metadataAssembly) + sourceAssembly:=previousSourceAssembly, + otherAssembly:=metadataAssembly, + otherSynthesizedTypes:=metadataSymbols.SynthesizedTypes) Dim previousSourceToCurrentSource As VisualBasicSymbolMatcher = Nothing If baseline.Ordinal > 0 Then @@ -76,9 +79,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit previousSourceToCurrentSource = New VisualBasicSymbolMatcher( sourceAssembly:=sourceAssembly, otherAssembly:=previousSourceAssembly, - baseline.SynthesizedTypes, - otherSynthesizedMembersOpt:=baseline.SynthesizedMembers, - otherDeletedMembersOpt:=baseline.DeletedMembers) + otherSynthesizedTypes:=baseline.SynthesizedTypes, + otherSynthesizedMembers:=baseline.SynthesizedMembers, + otherDeletedMembers:=baseline.DeletedMembers) End If definitionMap = New VisualBasicDefinitionMap(edits, metadataDecoder, previousSourceToMetadata, matchToMetadata, previousSourceToCurrentSource, baseline) @@ -178,7 +181,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Return previousGeneration End If - Dim synthesizedTypes = moduleBeingBuilt.GetSynthesizedTypes() + Dim currentSynthesizedTypes = moduleBeingBuilt.GetAllSynthesizedTypes() Dim currentSynthesizedMembers = moduleBeingBuilt.GetAllSynthesizedMembers() Dim currentDeletedMembers = moduleBeingBuilt.EncSymbolChanges.DeletedMembers @@ -186,27 +189,29 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Dim previousSourceAssembly = DirectCast(previousGeneration.Compilation, VisualBasicCompilation).SourceAssembly Dim matcher = New VisualBasicSymbolMatcher( - previousSourceAssembly, - compilation.SourceAssembly, - synthesizedTypes, - currentSynthesizedMembers, - currentDeletedMembers) + sourceAssembly:=previousSourceAssembly, + otherAssembly:=compilation.SourceAssembly, + otherSynthesizedTypes:=currentSynthesizedTypes, + otherSynthesizedMembers:=currentSynthesizedMembers, + otherDeletedMembers:=currentDeletedMembers) + Dim mappedSynthesizedTypes = matcher.MapSynthesizedTypes(previousGeneration.SynthesizedTypes, currentSynthesizedTypes) Dim mappedSynthesizedMembers = matcher.MapSynthesizedOrDeletedMembers(previousGeneration.SynthesizedMembers, currentSynthesizedMembers, isDeletedMemberMapping:=False) Dim mappedDeletedMembers = matcher.MapSynthesizedOrDeletedMembers(previousGeneration.DeletedMembers, currentDeletedMembers, isDeletedMemberMapping:=True) ' TODO can we reuse some data from the previous matcher? - Dim matcherWithAllSynthesizedMembers = New VisualBasicSymbolMatcher( - previousSourceAssembly, - compilation.SourceAssembly, - synthesizedTypes, - mappedSynthesizedMembers, - mappedDeletedMembers) - - Return matcherWithAllSynthesizedMembers.MapBaselineToCompilation( + Dim matcherWithAllSynthesizedTypesAndMembers = New VisualBasicSymbolMatcher( + sourceAssembly:=previousSourceAssembly, + otherAssembly:=compilation.SourceAssembly, + otherSynthesizedTypes:=mappedSynthesizedTypes, + otherSynthesizedMembers:=mappedSynthesizedMembers, + otherDeletedMembers:=mappedDeletedMembers) + + Return matcherWithAllSynthesizedTypesAndMembers.MapBaselineToCompilation( previousGeneration, compilation, moduleBeingBuilt, + mappedSynthesizedTypes, mappedSynthesizedMembers, mappedDeletedMembers) End Function diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/PEDeltaAssemblyBuilder.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/PEDeltaAssemblyBuilder.vb index 88ad55df98eed..7e08b71b2dc46 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/PEDeltaAssemblyBuilder.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/PEDeltaAssemblyBuilder.vb @@ -225,19 +225,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit End Get End Property - Friend Overloads Function GetSynthesizedTypes() As SynthesizedTypeMaps Implements IPEDeltaAssemblyBuilder.GetSynthesizedTypes - ' VB anonymous delegates are handled as anonymous types - Dim result = New SynthesizedTypeMaps( - Compilation.AnonymousTypeManager.GetAnonymousTypeMap(), - anonymousDelegates:=Nothing, - anonymousDelegatesWithIndexedNames:=Nothing) - - ' Should contain all entries in previous generation. - Debug.Assert(PreviousGeneration.SynthesizedTypes.IsSubsetOf(result)) - - Return result - End Function - Friend Overrides Function TryCreateVariableSlotAllocator(method As MethodSymbol, topLevelMethod As MethodSymbol, diagnostics As DiagnosticBag) As VariableSlotAllocator Return _changes.DefinitionMap.TryCreateVariableSlotAllocator(Compilation, method, topLevelMethod, diagnostics) End Function @@ -246,10 +233,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Return _changes.DefinitionMap.GetMethodBodyInstrumentations(method) End Function - Friend Overrides Function GetPreviousAnonymousTypes() As ImmutableArray(Of AnonymousTypeKey) - Return ImmutableArray.CreateRange(PreviousGeneration.SynthesizedTypes.AnonymousTypes.Keys) - End Function - Friend Overrides Function GetNextAnonymousTypeIndex(fromDelegates As Boolean) As Integer Return PreviousGeneration.GetNextAnonymousTypeIndex(fromDelegates) End Function diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb index be6752e8a4e07..05c3544cf7f67 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb @@ -6,6 +6,7 @@ Imports System.Collections.Concurrent Imports System.Collections.Immutable Imports System.Runtime.InteropServices Imports Microsoft.CodeAnalysis.CodeGen +Imports Microsoft.CodeAnalysis.Collections Imports Microsoft.CodeAnalysis.Emit Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Symbols @@ -23,18 +24,28 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Public Sub New(sourceAssembly As SourceAssemblySymbol, otherAssembly As SourceAssemblySymbol, - synthesizedTypes As SynthesizedTypeMaps, - otherSynthesizedMembersOpt As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), - otherDeletedMembersOpt As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal))) - - _visitor = New Visitor(sourceAssembly, otherAssembly, synthesizedTypes, otherSynthesizedMembersOpt, otherDeletedMembersOpt, New DeepTranslator(otherAssembly.GetSpecialType(SpecialType.System_Object))) + otherSynthesizedTypes As SynthesizedTypeMaps, + otherSynthesizedMembers As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), + otherDeletedMembers As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal))) + + _visitor = New Visitor(sourceAssembly, + otherAssembly, + otherSynthesizedTypes, + otherSynthesizedMembers, + otherDeletedMembers, + New DeepTranslator(otherAssembly.GetSpecialType(SpecialType.System_Object))) End Sub - Public Sub New(synthesizedTypes As SynthesizedTypeMaps, - sourceAssembly As SourceAssemblySymbol, - otherAssembly As PEAssemblySymbol) - - _visitor = New Visitor(sourceAssembly, otherAssembly, synthesizedTypes, otherSynthesizedMembersOpt:=Nothing, otherDeletedMembers:=Nothing, deepTranslatorOpt:=Nothing) + Public Sub New(sourceAssembly As SourceAssemblySymbol, + otherAssembly As PEAssemblySymbol, + otherSynthesizedTypes As SynthesizedTypeMaps) + + _visitor = New Visitor(sourceAssembly, + otherAssembly, + otherSynthesizedTypes, + otherSynthesizedMembers:=SpecializedCollections.EmptyReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), + otherDeletedMembers:=SpecializedCollections.EmptyReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), + deepTranslatorOpt:=Nothing) End Sub Public Overrides Function MapDefinition(definition As Cci.IDefinition) As Cci.IDefinition @@ -70,6 +81,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Return _visitor.TryGetAnonymousTypeName(template, name, index) End Function + Protected Overrides Function TryGetMatchingDelegateWithIndexedName(delegateTemplate As INamedTypeSymbolInternal, values As ImmutableArray(Of AnonymousTypeValue), ByRef match As AnonymousTypeValue) As Boolean + ' VB does not have delegates with indexed names + Throw ExceptionUtilities.Unreachable() + End Function + Private NotInheritable Class Visitor Inherits VisualBasicSymbolVisitor(Of Symbol) @@ -79,7 +95,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Private ReadOnly _sourceAssembly As SourceAssemblySymbol Private ReadOnly _otherAssembly As AssemblySymbol - Private ReadOnly _otherSynthesizedMembersOpt As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)) + Private ReadOnly _otherSynthesizedMembers As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)) Private ReadOnly _otherDeletedMembersOpt As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)) ' A cache of members per type, populated when the first member for a given @@ -91,14 +107,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Public Sub New(sourceAssembly As SourceAssemblySymbol, otherAssembly As AssemblySymbol, synthesizedTypes As SynthesizedTypeMaps, - otherSynthesizedMembersOpt As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), + otherSynthesizedMembers As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), otherDeletedMembers As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), deepTranslatorOpt As DeepTranslator) _synthesizedTypes = synthesizedTypes _sourceAssembly = sourceAssembly _otherAssembly = otherAssembly - _otherSynthesizedMembersOpt = otherSynthesizedMembersOpt + _otherSynthesizedMembers = otherSynthesizedMembers _otherDeletedMembersOpt = otherDeletedMembers _comparer = New SymbolComparer(Me, deepTranslatorOpt) _matches = New ConcurrentDictionary(Of Symbol, Symbol)(ReferenceEqualityComparer.Instance) @@ -356,8 +372,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit End Function Friend Function TryFindAnonymousType(type As AnonymousTypeManager.AnonymousTypeOrDelegateTemplateSymbol, ByRef otherType As AnonymousTypeValue) As Boolean - Debug.Assert(type.ContainingSymbol Is _sourceAssembly.GlobalNamespace) - Return _synthesizedTypes.AnonymousTypes.TryGetValue(type.GetAnonymousTypeKey(), otherType) End Function @@ -519,12 +533,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit End If Dim synthesizedMembers As ImmutableArray(Of ISymbolInternal) = Nothing - If _otherSynthesizedMembersOpt IsNot Nothing AndAlso _otherSynthesizedMembersOpt.TryGetValue(symbol, synthesizedMembers) Then + If _otherSynthesizedMembers.TryGetValue(symbol, synthesizedMembers) Then members.AddRange(synthesizedMembers) End If Dim deletedMembers As ImmutableArray(Of ISymbolInternal) = Nothing - If _otherDeletedMembersOpt IsNot Nothing AndAlso _otherDeletedMembersOpt.TryGetValue(symbol, deletedMembers) Then + If _otherDeletedMembersOpt.TryGetValue(symbol, deletedMembers) Then members.AddRange(deletedMembers) End If diff --git a/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb index faefdac0799dc..d07e661d83163 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb @@ -325,10 +325,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Return New MethodInstrumentation() With {.Kinds = EmitOptions.InstrumentationKinds} End Function - Friend Overridable Function GetPreviousAnonymousTypes() As ImmutableArray(Of AnonymousTypeKey) - Return ImmutableArray(Of AnonymousTypeKey).Empty - End Function - Friend Overridable Function GetNextAnonymousTypeIndex(fromDelegates As Boolean) As Integer Return 0 End Function diff --git a/src/Compilers/VisualBasic/Portable/Symbols/AnonymousTypes/AnonymousTypeManager_Templates.vb b/src/Compilers/VisualBasic/Portable/Symbols/AnonymousTypes/AnonymousTypeManager_Templates.vb index 801c74b7f5a48..409fef44407ff 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/AnonymousTypes/AnonymousTypeManager_Templates.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/AnonymousTypes/AnonymousTypeManager_Templates.vb @@ -3,13 +3,12 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Concurrent -Imports System.Collections.Generic Imports System.Collections.Immutable -Imports System.Diagnostics Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Collections Imports Microsoft.CodeAnalysis.PooledObjects +Imports Microsoft.CodeAnalysis.Emit Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols @@ -172,18 +171,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ''' anonymous type methods. Also seals the collection of templates. ''' Public Sub AssignTemplatesNamesAndCompile(compiler As MethodCompiler, moduleBeingBuilt As Emit.PEModuleBuilder, diagnostics As BindingDiagnosticBag) - - ' Ensure all previous anonymous type templates are included so the - ' types are available for subsequent edit and continue generations. - For Each key In moduleBeingBuilt.GetPreviousAnonymousTypes() - Dim templateKey = AnonymousTypeDescriptor.ComputeKey(key.Fields, Function(f) f.Name, Function(f) f.IsKey) - If key.IsDelegate Then - AnonymousDelegateTemplates.GetOrAdd(templateKey, Function(k) AnonymousDelegateTemplateSymbol.Create(Me, CreatePlaceholderTypeDescriptor(key))) - Else - AnonymousTypeTemplates.GetOrAdd(templateKey, Function(k) New AnonymousTypeTemplateSymbol(Me, CreatePlaceholderTypeDescriptor(key))) - End If - Next - ' Get all anonymous types owned by this manager Dim builder = ArrayBuilder(Of AnonymousTypeOrDelegateTemplateSymbol).GetInstance() GetAllCreatedTemplates(builder) @@ -192,23 +179,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ' to the created anonymous type and delegate templates If Not Me.AreTemplatesSealed Then - ' If we are emitting .NET module, include module's name into type's name to ensure - ' uniqueness across added modules. - Dim moduleId As String - - If moduleBeingBuilt.OutputKind = OutputKind.NetModule Then - moduleId = moduleBeingBuilt.Name - Dim extension As String = OutputKind.NetModule.GetDefaultExtension() - - If moduleId.EndsWith(extension, StringComparison.OrdinalIgnoreCase) Then - moduleId = moduleId.Substring(0, moduleId.Length - extension.Length) - End If - - moduleId = "<" & MetadataHelpers.MangleForTypeNameIfNeeded(moduleId) & ">" - Else - moduleId = String.Empty - End If - + Dim moduleId = GetModuleId(moduleBeingBuilt) Dim typeIndex = moduleBeingBuilt.GetNextAnonymousTypeIndex(fromDelegates:=False) Dim delegateIndex = moduleBeingBuilt.GetNextAnonymousTypeIndex(fromDelegates:=True) For Each template In builder @@ -248,6 +219,24 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols builder.Free() End Sub + Function GetModuleId(moduleBeingBuilt As Emit.PEModuleBuilder) As String + ' If we are emitting .NET module, include module's name into type's name to ensure + ' uniqueness across added modules. + + If moduleBeingBuilt.OutputKind = OutputKind.NetModule Then + Dim moduleId = moduleBeingBuilt.Name + Dim extension As String = OutputKind.NetModule.GetDefaultExtension() + + If moduleId.EndsWith(extension, StringComparison.OrdinalIgnoreCase) Then + moduleId = moduleId.Substring(0, moduleId.Length - extension.Length) + End If + + Return "<" & MetadataHelpers.MangleForTypeNameIfNeeded(moduleId) & ">" + Else + Return String.Empty + End If + End Function + Friend Function GetAnonymousTypeMap() As ImmutableSegmentedDictionary(Of Microsoft.CodeAnalysis.Emit.AnonymousTypeKey, Microsoft.CodeAnalysis.Emit.AnonymousTypeValue) Dim templates = ArrayBuilder(Of AnonymousTypeOrDelegateTemplateSymbol).GetInstance() GetAllCreatedTemplates(templates) @@ -304,6 +293,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End If End Sub + Friend Overrides Function GetSynthesizedTypeMaps() As SynthesizedTypeMaps + ' VB anonymous delegates are handled as anonymous types + Return New SynthesizedTypeMaps( + GetAnonymousTypeMap(), + anonymousDelegates:=Nothing, + anonymousDelegatesWithIndexedNames:=Nothing) + End Function + Private NotInheritable Class AnonymousTypeComparer Implements IComparer(Of AnonymousTypeOrDelegateTemplateSymbol) diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.vb index 21060764f7d1b..19290e235fcb6 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.vb @@ -8,9 +8,11 @@ Imports System.Reflection.Metadata Imports System.Reflection.Metadata.Ecma335 Imports System.Runtime.CompilerServices Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Collections Imports Microsoft.CodeAnalysis.EditAndContinue.UnitTests Imports Microsoft.CodeAnalysis.Emit Imports Microsoft.CodeAnalysis.PooledObjects +Imports Microsoft.CodeAnalysis.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Emit Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -307,11 +309,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests Friend Shared Function CreateMatcher(fromCompilation As VisualBasicCompilation, toCompilation As VisualBasicCompilation) As VisualBasicSymbolMatcher Return New VisualBasicSymbolMatcher( - fromCompilation.SourceAssembly, - toCompilation.SourceAssembly, - synthesizedTypes:=SynthesizedTypeMaps.Empty, - otherSynthesizedMembersOpt:=Nothing, - otherDeletedMembersOpt:=Nothing) + sourceAssembly:=fromCompilation.SourceAssembly, + otherAssembly:=toCompilation.SourceAssembly, + otherSynthesizedTypes:=SynthesizedTypeMaps.Empty, + otherSynthesizedMembers:=SpecializedCollections.EmptyReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), + otherDeletedMembers:=SpecializedCollections.EmptyReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal))) End Function End Class diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb index 8193d204d35d7..04d8dbd00cd25 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb @@ -4943,91 +4943,121 @@ End Class Public Sub AnonymousTypes() - Dim sources0 = - - - Dim sources1 = - - - Dim compilation0 = CreateCompilationWithMscorlib40(sources0, options:=TestOptions.DebugDll) - Dim compilation1 = compilation0.WithSource(sources1) + Using test = New EditAndContinueTest() + test.AddBaseline( + source:="Module C + Sub F() + Dim f = New With { .a = 1, .b = 2 } + End Sub +End Module", + validator:= + Sub(g) + g.VerifySynthesizedMembers({}) - Dim testData0 = New CompilationTestData() - Dim bytes0 = compilation0.EmitToArray(testData:=testData0) - Using md0 = ModuleMetadata.CreateFromImage(bytes0) - Dim generation0 = CreateInitialBaseline(compilation0, ModuleMetadata.CreateFromImage(bytes0), - testData0.GetMethodData("M.B.M").EncDebugInfoProvider) - Dim method0 = compilation0.GetMember(Of MethodSymbol)("M.B.M") - Dim reader0 = md0.MetadataReader - CheckNames(reader0, reader0.GetTypeDefNames(), - "", - "VB$AnonymousType_0`2", - "VB$AnonymousType_1`2", - "VB$AnonymousType_2`1", - "A", - "B") - Dim method1 = compilation1.GetMember(Of MethodSymbol)("M.B.M") - Dim diff1 = compilation1.EmitDifference( - generation0, - ImmutableArray.Create(New SemanticEdit(SemanticEditKind.Update, method0, method1, GetEquivalentNodesMap(method1, method0)))) + g.VerifySynthesizedTypes( + "VB$AnonymousType_0(Of T0, T1)") - Using md1 = diff1.GetMetadata() - Dim reader1 = md1.Reader - CheckNames({reader0, reader1}, reader1.GetTypeDefNames(), "VB$AnonymousType_3`1") - diff1.VerifyIL("M.B.M", " + g.VerifyIL("C.F", " { - // Code size 29 (0x1d) + // Code size 10 (0xa) .maxstack 2 - .locals init (VB$AnonymousType_1(Of Integer, Integer) V_0, //x - [int] V_1, - VB$AnonymousType_2(Of Integer) V_2, //z - VB$AnonymousType_3(Of Integer) V_3) //y + .locals init (VB$AnonymousType_0(Of Integer, Integer) V_0) //f IL_0000: nop - IL_0001: ldc.i4.3 - IL_0002: ldc.i4.4 - IL_0003: newobj ""Sub VB$AnonymousType_1(Of Integer, Integer)..ctor(Integer, Integer)"" + IL_0001: ldc.i4.1 + IL_0002: ldc.i4.2 + IL_0003: newobj ""Sub VB$AnonymousType_0(Of Integer, Integer)..ctor(Integer, Integer)"" IL_0008: stloc.0 - IL_0009: ldloc.0 - IL_000a: callvirt ""Function VB$AnonymousType_1(Of Integer, Integer).get_A() As Integer"" - IL_000f: newobj ""Sub VB$AnonymousType_3(Of Integer)..ctor(Integer)"" - IL_0014: stloc.3 - IL_0015: ldc.i4.5 - IL_0016: newobj ""Sub VB$AnonymousType_2(Of Integer)..ctor(Integer)"" - IL_001b: stloc.2 - IL_001c: ret -} -") - End Using + IL_0009: ret +}") + End Sub). + AddGeneration(' 1 + source:="Module C + Sub F() + Dim g = New With { .x = 1 } + Dim f = New With { .a = 1, .b = 2 } + End Sub +End Module", + edits:= + { + Edit(SemanticEditKind.Update, Function(c) c.GetMember("C.F"), preserveLocalVariables:=True) + }, + validator:= + Sub(g) + g.VerifySynthesizedMembers({}) + + g.VerifySynthesizedTypes( + "VB$AnonymousType_0(Of T0, T1)", + "VB$AnonymousType_1(Of T0)") + + g.VerifyTypeDefNames("VB$AnonymousType_1`1") + g.VerifyMethodDefNames("F", "get_x", "set_x", ".ctor", "ToString") + + g.VerifyIL("C.F", " +{ + // Code size 17 (0x11) + .maxstack 2 + .locals init (VB$AnonymousType_0(Of Integer, Integer) V_0, //f + VB$AnonymousType_1(Of Integer) V_1) //g + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: newobj ""Sub VB$AnonymousType_1(Of Integer)..ctor(Integer)"" + IL_0007: stloc.1 + IL_0008: ldc.i4.1 + IL_0009: ldc.i4.2 + IL_000a: newobj ""Sub VB$AnonymousType_0(Of Integer, Integer)..ctor(Integer, Integer)"" + IL_000f: stloc.0 + IL_0010: ret +}") + End Sub). + AddGeneration(' 2 + source:=" +Module C + Sub F() + Dim f = New With { .a = 1, .b = 2, .c = 3 } + End Sub +End Module", + edits:= + { + Edit(SemanticEditKind.Update, Function(c) c.GetMember("C.F"), preserveLocalVariables:=True) + }, + validator:= + Sub(g) + g.VerifySynthesizedMembers({}) + + g.VerifySynthesizedTypes( + "VB$AnonymousType_0(Of T0, T1)", + "VB$AnonymousType_1(Of T0)", + "VB$AnonymousType_2(Of T0, T1, T2)") + + g.VerifyTypeDefNames("VB$AnonymousType_2`3") + g.VerifyMethodDefNames("F", "get_a", "set_a", "get_b", "set_b", "get_c", "set_c", ".ctor", "ToString") + End Sub). + AddGeneration(' 3 + source:=" + Module C + Sub F() + Dim f = New With { .a = 1, .b = 2, .c = 3 } + Dim g = New With { .x = 1, .y = 2 } + End Sub + End Module", + edits:= + { + Edit(SemanticEditKind.Update, Function(c) c.GetMember("C.F"), preserveLocalVariables:=True) + }, + validator:= + Sub(g) + g.VerifySynthesizedMembers({}) + + g.VerifySynthesizedTypes( + "VB$AnonymousType_0(Of T0, T1)", + "VB$AnonymousType_1(Of T0)", + "VB$AnonymousType_2(Of T0, T1, T2)", + "VB$AnonymousType_3(Of T0, T1)") + + g.VerifyTypeDefNames("VB$AnonymousType_3`2") + g.VerifyMethodDefNames("F", "get_x", "set_x", "get_y", "set_y", ".ctor", "ToString") + End Sub). + Verify() End Using End Sub @@ -5738,6 +5768,115 @@ End Class ") End Sub + + Public Sub Lambda_SynthesizedDelegate() + Using test = New EditAndContinueTest() + test.AddBaseline( + source:=" +Class C + Sub F() + Dim f = Function(ByRef a As Integer) a + End Sub +End Class", + validator:= + Sub(g) + g.VerifySynthesizedMembers( + { + "C: {_Closure$__}", + "C._Closure$__: {$I1-0, _Lambda$__1-0}" + }) + + g.VerifySynthesizedTypes( + "VB$AnonymousDelegate_0(Of TArg0, TResult)") + End Sub). + AddGeneration(' 1 + source:=" +Class C + Sub F() + Dim g = Function(ByRef a As Byte, b As Integer) a + Dim f = Function(ByRef a As Integer) a + End Sub +End Class", + edits:= + { + Edit(SemanticEditKind.Update, Function(c) c.GetMember("C.F"), preserveLocalVariables:=True) + }, + validator:= + Sub(g) + g.VerifySynthesizedMembers( + { + "C: {_Closure$__}", + "C._Closure$__: {$I1-0#1, $I1-0, _Lambda$__1-0#1, _Lambda$__1-0}" + }) + + g.VerifySynthesizedTypes( + "VB$AnonymousDelegate_0(Of TArg0, TResult)", + "VB$AnonymousDelegate_1(Of TArg0, TArg1, TResult)") + + g.VerifyMethodDefNames("F", "_Lambda$__1-0", ".ctor", "BeginInvoke", "EndInvoke", "Invoke", "_Lambda$__1-0#1") + End Sub). + AddGeneration(' 2 + source:=" +Class C + Sub F() + Dim f = Function(ByRef a As Boolean, ByRef b As Boolean) a + End Sub +End Class", + edits:= + { + Edit(SemanticEditKind.Update, Function(c) c.GetMember("C.F"), preserveLocalVariables:=True, rudeEdits:=Function(node) New RuntimeRudeEdit("Parameter changed", &H123)) + }, + validator:= + Sub(g) + g.VerifySynthesizedMembers( + { + "System.Runtime.CompilerServices.HotReloadException", + "C: {_Closure$__}", + "C._Closure$__: {$I1-0#2, _Lambda$__1-0#2, $I1-0#1, $I1-0, _Lambda$__1-0#1, _Lambda$__1-0}" + }) + + g.VerifySynthesizedTypes( + "VB$AnonymousDelegate_0(Of TArg0, TResult)", + "VB$AnonymousDelegate_1(Of TArg0, TArg1, TResult)", + "VB$AnonymousDelegate_2(Of TArg0, TArg1, TResult)") + + g.VerifyTypeDefNames("VB$AnonymousDelegate_2`3", "HotReloadException") + g.VerifyMethodDefNames("F", "_Lambda$__1-0", "_Lambda$__1-0#1", ".ctor", "BeginInvoke", "EndInvoke", "Invoke", ".ctor", "_Lambda$__1-0#2") + End Sub). + AddGeneration(' 3 + source:=" +Class C + Sub F() + Dim f = Function(ByRef a As Boolean, ByRef b As Boolean) a + Dim g = Function(ByRef a As Boolean, ByRef b As Boolean, c As Boolean) a + End Sub +End Class", + edits:= + { + Edit(SemanticEditKind.Update, Function(c) c.GetMember("C.F"), preserveLocalVariables:=True) + }, + validator:= + Sub(g) + g.VerifySynthesizedMembers( + { + "System.Runtime.CompilerServices.HotReloadException", + "C._Closure$__: {$I1-0#3, $I1-1#3, _Lambda$__1-0#3, _Lambda$__1-1#3, $I1-0#2, _Lambda$__1-0#2, $I1-0#1, $I1-0, _Lambda$__1-0#1, _Lambda$__1-0}", + "C: {_Closure$__}" + }) + + g.VerifySynthesizedTypes( + "VB$AnonymousDelegate_0(Of TArg0, TResult)", + "VB$AnonymousDelegate_1(Of TArg0, TArg1, TResult)", + "VB$AnonymousDelegate_2(Of TArg0, TArg1, TResult)", + "VB$AnonymousDelegate_3(Of TArg0, TArg1, TArg2, TResult)") + + g.VerifyTypeDefNames("VB$AnonymousDelegate_3`4") + g.VerifyMethodDefNames("F", "_Lambda$__1-0#2", ".ctor", "BeginInvoke", "EndInvoke", "Invoke", "_Lambda$__1-0#3", "_Lambda$__1-1#3") + End Sub). + Verify() + End Using + End Sub + ''' ''' Should not re-use locals with custom modifiers. ''' diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.vb index f6720503c9293..5926fdfb012f3 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.vb @@ -355,9 +355,9 @@ End Class Assert.Equal("$VB$Local_x2", x2.Name) Dim matcher = New VisualBasicSymbolMatcher( - synthesizedTypes0, compilation1.SourceAssembly, - peAssemblySymbol0) + peAssemblySymbol0, + synthesizedTypes0) Dim mappedX1 = DirectCast(matcher.MapDefinition(x1), Cci.IFieldDefinition) Dim mappedX2 = DirectCast(matcher.MapDefinition(x2), Cci.IFieldDefinition) @@ -426,9 +426,9 @@ End Class Assert.Equal("$VB$Local_x2", x2.Name) Dim matcher = New VisualBasicSymbolMatcher( - synthesizedTypes0, compilation1.SourceAssembly, - peAssemblySymbol0) + peAssemblySymbol0, + synthesizedTypes0) Dim mappedX1 = DirectCast(matcher.MapDefinition(x1), Cci.IFieldDefinition) Dim mappedX2 = DirectCast(matcher.MapDefinition(x2), Cci.IFieldDefinition) @@ -503,9 +503,9 @@ End Class Assert.Equal("$VB$Local_x2", x2.Name) Dim matcher = New VisualBasicSymbolMatcher( - synthesizedTypes0, compilation1.SourceAssembly, - peAssemblySymbol0) + peAssemblySymbol0, + synthesizedTypes0) Dim mappedX1 = DirectCast(matcher.MapDefinition(x1), Cci.IFieldDefinition) Dim mappedX2 = DirectCast(matcher.MapDefinition(x2), Cci.IFieldDefinition) diff --git a/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs index a3e54b065954d..3f67252c8104a 100644 --- a/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -3755,10 +3755,46 @@ void F() } [Fact] - public void Lambdas_Update_Signature_ParameterRefness1() + [WorkItem("https://github.com/dotnet/roslyn/issues/79783")] + public void Lambdas_Update_Signature_ParameterDefaultValue() { var src1 = """ + using System; + + class C + { + void F() + { + var f = (int a = 1) => 1; + } + } + """; + var src2 = """ + using System; + + class C + { + void F() + { + var f = (int a = 2) => 1; + } + } + """; + var edits = GetTopEdits(src1, src2); + var syntaxMap = edits.GetSyntaxMap(); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), syntaxMap, rudeEdits: + [ + RuntimeRudeEdit(0, RudeEditKind.ChangingLambdaParameters, syntaxMap.NodePosition(0), arguments: [GetResource("lambda")]) + ])); + } + + [Fact] + public void Lambdas_Update_Signature_ParameterRefness1() + { + var src1 = """ using System; delegate int D1(ref int a); @@ -3774,10 +3810,8 @@ void F() G1((ref int a) => 1); } } - """; var src2 = """ - using System; delegate int D1(ref int a); @@ -3793,8 +3827,8 @@ void F() G2((int a) => 2); } } - """; + var edits = GetTopEdits(src1, src2); var syntaxMap = edits.GetSyntaxMap(); diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index e96503cc24680..564dcf854d977 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -2544,6 +2544,9 @@ protected static bool SymbolsEquivalent(ISymbol oldSymbol, ISymbol newSymbol) protected static bool ParameterTypesEquivalent(ImmutableArray oldParameters, ImmutableArray newParameters, bool exact) => oldParameters.SequenceEqual(newParameters, exact, ParameterTypesEquivalent); + protected static bool ParameterDefaultValuesEquivalent(ImmutableArray oldParameters, ImmutableArray newParameters) + => oldParameters.SequenceEqual(newParameters, ParameterDefaultValuesEquivalent); + protected static bool CustomModifiersEquivalent(CustomModifier oldModifier, CustomModifier newModifier, bool exact) => oldModifier.IsOptional == newModifier.IsOptional && TypesEquivalent(oldModifier.Modifier, newModifier.Modifier, exact); @@ -2582,6 +2585,10 @@ protected static bool TypesEquivalent(ImmutableArray oldTypes, ImmutableAr protected static bool ParameterTypesEquivalent(IParameterSymbol oldParameter, IParameterSymbol newParameter, bool exact) => (exact ? s_exactSymbolEqualityComparer : s_runtimeSymbolEqualityComparer).ParameterEquivalenceComparer.Equals(oldParameter, newParameter); + protected static bool ParameterDefaultValuesEquivalent(IParameterSymbol oldParameter, IParameterSymbol newParameter) + => oldParameter.HasExplicitDefaultValue == newParameter.HasExplicitDefaultValue && + (!oldParameter.HasExplicitDefaultValue || Equals(oldParameter.ExplicitDefaultValue, newParameter.ExplicitDefaultValue)); + protected static bool TypeParameterConstraintsEquivalent(ITypeParameterSymbol oldParameter, ITypeParameterSymbol newParameter, bool exact) => TypesEquivalent(oldParameter.ConstraintTypes, newParameter.ConstraintTypes, exact) && oldParameter.HasReferenceTypeConstraint == newParameter.HasReferenceTypeConstraint && @@ -4490,8 +4497,7 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( hasGeneratedAttributeChange = true; } - if (oldParameter.HasExplicitDefaultValue != newParameter.HasExplicitDefaultValue || - oldParameter.HasExplicitDefaultValue && !Equals(oldParameter.ExplicitDefaultValue, newParameter.ExplicitDefaultValue)) + if (!ParameterDefaultValuesEquivalent(oldParameter, newParameter)) { rudeEdit = RudeEditKind.InitializerUpdate; } @@ -6572,8 +6578,13 @@ private void ReportLambdaSignatureRudeEdits( var newLambdaSymbol = (IMethodSymbol)diagnosticContext.RequiredNewSymbol; // signature validation: - if (!ParameterTypesEquivalent(oldLambdaSymbol.Parameters, newLambdaSymbol.Parameters, exact: false)) + if (!ParameterTypesEquivalent(oldLambdaSymbol.Parameters, newLambdaSymbol.Parameters, exact: false) || + !ParameterDefaultValuesEquivalent(oldLambdaSymbol.Parameters, newLambdaSymbol.Parameters)) { + // If a delegate type for the lambda is synthesized (anonymous) changing default parameter value changes the synthesized delegate type. + // If the delegate type is not synthesized the default value is ignored and warning is reported by the compiler. + // Technically, the runtime rude edit does not need to be reported in the latter case but we report it anyway for simplicity. + runtimeRudeEditsBuilder[newLambda] = diagnosticContext.CreateRudeEdit(RudeEditKind.ChangingLambdaParameters, cancellationToken); return; } @@ -6585,7 +6596,7 @@ private void ReportLambdaSignatureRudeEdits( } if (!TypeParametersEquivalent(oldLambdaSymbol.TypeParameters, newLambdaSymbol.TypeParameters, exact: false) || - !oldLambdaSymbol.TypeParameters.SequenceEqual(newLambdaSymbol.TypeParameters, static (p, q) => p.Name == q.Name)) + !oldLambdaSymbol.TypeParameters.SequenceEqual(newLambdaSymbol.TypeParameters, static (p, q) => p.Name == q.Name)) { runtimeRudeEditsBuilder[newLambda] = diagnosticContext.CreateRudeEdit(RudeEditKind.ChangingTypeParameters, cancellationToken); return; diff --git a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs index 63be4ad35bae0..5ee0a89459dc8 100644 --- a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs +++ b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; +using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Symbols; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; @@ -119,9 +120,19 @@ internal void VerifyCustomAttributes(IEnumerable? expected = }); private IReadOnlyDictionary> GetSynthesizedMembers() - => (generationInfo.CompilationVerifier != null) - ? generationInfo.CompilationVerifier.TestData.Module!.GetAllSynthesizedMembers() - : generationInfo.Baseline.SynthesizedMembers; + => generationInfo.CompilationVerifier?.TestData.Module!.GetAllSynthesizedMembers() ?? generationInfo.Baseline.SynthesizedMembers; + + public ImmutableArray GetSynthesizedTypes() + { + var map = generationInfo.CompilationVerifier?.TestData.Module!.GetAllSynthesizedTypes() ?? generationInfo.Baseline.SynthesizedTypes; + + return + [ + .. map.AnonymousTypes.Values.Select(t => t.Type.GetInternalSymbol()!), + .. map.AnonymousDelegates.Values.Select(t => t.Delegate.GetInternalSymbol()!), + .. map.AnonymousDelegatesWithIndexedNames.Values.SelectMany(t => t.Select(d => d.Type.GetInternalSymbol()!)) + ]; + } public void VerifySynthesizedMembers(params string[] expected) => VerifySynthesizedMembers(displayTypeKind: false, expected); @@ -129,6 +140,9 @@ public void VerifySynthesizedMembers(params string[] expected) public void VerifySynthesizedMembers(bool displayTypeKind, params string[] expected) => Verify(() => CompilationDifference.VerifySynthesizedMembers(GetSynthesizedMembers(), displayTypeKind, expected)); + public void VerifySynthesizedTypes(params string[] expected) + => Verify(() => CompilationDifference.VerifySynthesizedSymbols(GetSynthesizedTypes(), expected)); + public void VerifySynthesizedFields(string typeName, params string[] expectedSynthesizedTypesAndMemberCounts) => Verify(() => { From 833f5e210f63a8b50d592436d94fd74ba09de1dd Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Thu, 7 Aug 2025 18:10:18 -0700 Subject: [PATCH 2/4] Cleanup --- .../Portable/Compilation/CSharpCompilation.cs | 15 ++++- .../Emitter/EditAndContinue/EmitHelpers.cs | 66 ------------------- .../Core/Portable/Compilation/Compilation.cs | 65 +++++++++++++++++- .../Emit/EditAndContinue/SymbolMatcher.cs | 49 +++++--------- .../Compilation/VisualBasicCompilation.vb | 14 +++- .../Emit/EditAndContinue/EmitHelpers.vb | 49 -------------- 6 files changed, 107 insertions(+), 151 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 21586fb1dc52c..ebfea53f6b963 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -3598,8 +3598,19 @@ internal override bool CompileMethods( return true; } - private protected override EmitBaseline MapToCompilation(CommonPEModuleBuilder moduleBeingBuilt) - => EmitHelpers.MapToCompilation(this, (PEDeltaAssemblyBuilder)moduleBeingBuilt); + private protected override SymbolMatcher CreatePreviousToCurrentSourceAssemblyMatcher( + EmitBaseline previousGeneration, + SynthesizedTypeMaps otherSynthesizedTypes, + IReadOnlyDictionary> otherSynthesizedMembers, + IReadOnlyDictionary> otherDeletedMembers) + { + return new CSharpSymbolMatcher( + sourceAssembly: ((CSharpCompilation)previousGeneration.Compilation).SourceAssembly, + SourceAssembly, + otherSynthesizedTypes, + otherSynthesizedMembers, + otherDeletedMembers); + } private class DuplicateFilePathsVisitor : CSharpSymbolVisitor { diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs index 87ac714731ef1..a70bbde698382 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Reflection.Metadata; @@ -15,7 +14,6 @@ using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Emit { @@ -178,69 +176,5 @@ private static bool GetPredefinedHotReloadExceptionTypeConstructor(CSharpCompila return false; } - - /// - /// Return a version of the baseline with all definitions mapped to this compilation. - /// Definitions from the initial generation, from metadata, are not mapped since - /// the initial generation is always included as metadata. That is, the symbols from - /// types, methods, ... in the TypesAdded, MethodsAdded, ... collections are replaced - /// by the corresponding symbols from the current compilation. - /// - internal static EmitBaseline MapToCompilation( - CSharpCompilation compilation, - PEDeltaAssemblyBuilder moduleBeingBuilt) - { - var previousGeneration = moduleBeingBuilt.PreviousGeneration; - RoslynDebug.Assert(previousGeneration.Compilation != compilation); - - if (previousGeneration.Ordinal == 0) - { - // Initial generation, nothing to map. (Since the initial generation - // is always loaded from metadata in the context of the current - // compilation, there's no separate mapping step.) - return previousGeneration; - } - - RoslynDebug.AssertNotNull(previousGeneration.Compilation); - RoslynDebug.AssertNotNull(previousGeneration.PEModuleBuilder); - RoslynDebug.AssertNotNull(moduleBeingBuilt.EncSymbolChanges); - - var currentSynthesizedTypes = moduleBeingBuilt.GetAllSynthesizedTypes(); - var currentSynthesizedMembers = moduleBeingBuilt.GetAllSynthesizedMembers(); - var currentDeletedMembers = moduleBeingBuilt.EncSymbolChanges.DeletedMembers; - - // Mapping from previous compilation to the current. - var previousSourceAssembly = ((CSharpCompilation)previousGeneration.Compilation).SourceAssembly; - - var matcher = new CSharpSymbolMatcher( - sourceAssembly: previousSourceAssembly, - otherAssembly: compilation.SourceAssembly, - otherSynthesizedTypes: currentSynthesizedTypes, - otherSynthesizedMembers: currentSynthesizedMembers, - otherDeletedMembers: currentDeletedMembers); - - var mappedSynthesizedTypes = matcher.MapSynthesizedTypes(previousGeneration.SynthesizedTypes, currentSynthesizedTypes); - - var mappedSynthesizedMembers = matcher.MapSynthesizedOrDeletedMembers(previousGeneration.SynthesizedMembers, currentSynthesizedMembers, isDeletedMemberMapping: false); - - // Deleted members are mapped the same way as synthesized members, so we can just call the same method. - var mappedDeletedMembers = matcher.MapSynthesizedOrDeletedMembers(previousGeneration.DeletedMembers, currentDeletedMembers, isDeletedMemberMapping: true); - - // TODO: can we reuse some data from the previous matcher? - var matcherWithAllSynthesizedTypesAndMembers = new CSharpSymbolMatcher( - sourceAssembly: previousSourceAssembly, - otherAssembly: compilation.SourceAssembly, - otherSynthesizedTypes: mappedSynthesizedTypes, - otherSynthesizedMembers: mappedSynthesizedMembers, - otherDeletedMembers: mappedDeletedMembers); - - return matcherWithAllSynthesizedTypesAndMembers.MapBaselineToCompilation( - previousGeneration, - compilation, - moduleBeingBuilt, - mappedSynthesizedTypes, - mappedSynthesizedMembers, - mappedDeletedMembers); - } } } diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index e09234674e1c9..ac7f2b58d71bf 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -3435,7 +3435,70 @@ internal static bool SerializePeToStream( return true; } - private protected abstract EmitBaseline MapToCompilation(CommonPEModuleBuilder moduleBeingBuilt); + /// + /// Return a version of the baseline with all definitions mapped to this compilation. + /// Definitions from the initial generation, from metadata, are not mapped since + /// the initial generation is always included as metadata. That is, the symbols from + /// types, methods, ... in the TypesAdded, MethodsAdded, ... collections are replaced + /// by the corresponding symbols from the current compilation. + /// + internal EmitBaseline MapToCompilation(CommonPEModuleBuilder moduleBeingBuilt) + { + var previousGeneration = moduleBeingBuilt.PreviousGeneration; + Debug.Assert(previousGeneration != null); + Debug.Assert(previousGeneration.Compilation != this); + + if (previousGeneration.Ordinal == 0) + { + // Initial generation, nothing to map. (Since the initial generation + // is always loaded from metadata in the context of the current + // compilation, there's no separate mapping step.) + return previousGeneration; + } + + Debug.Assert(previousGeneration.Compilation != null); + Debug.Assert(previousGeneration.PEModuleBuilder != null); + Debug.Assert(moduleBeingBuilt.EncSymbolChanges != null); + + var currentSynthesizedTypes = moduleBeingBuilt.GetAllSynthesizedTypes(); + var currentSynthesizedMembers = moduleBeingBuilt.GetAllSynthesizedMembers(); + var currentDeletedMembers = moduleBeingBuilt.EncSymbolChanges.DeletedMembers; + + // Mapping from previous compilation to the current. + var matcher = CreatePreviousToCurrentSourceAssemblyMatcher( + previousGeneration, + currentSynthesizedTypes, + currentSynthesizedMembers, + currentDeletedMembers); + + var mappedSynthesizedTypes = matcher.MapSynthesizedTypes(previousGeneration.SynthesizedTypes, currentSynthesizedTypes); + + var mappedSynthesizedMembers = matcher.MapSynthesizedOrDeletedMembers(previousGeneration.SynthesizedMembers, currentSynthesizedMembers, isDeletedMemberMapping: false); + + // Deleted members are mapped the same way as synthesized members, so we can just call the same method. + var mappedDeletedMembers = matcher.MapSynthesizedOrDeletedMembers(previousGeneration.DeletedMembers, currentDeletedMembers, isDeletedMemberMapping: true); + + // TODO: can we reuse some data from the previous matcher? + var matcherWithAllSynthesizedTypesAndMembers = CreatePreviousToCurrentSourceAssemblyMatcher( + previousGeneration, + mappedSynthesizedTypes, + mappedSynthesizedMembers, + mappedDeletedMembers); + + return matcherWithAllSynthesizedTypesAndMembers.MapBaselineToCompilation( + previousGeneration, + this, + moduleBeingBuilt, + mappedSynthesizedTypes, + mappedSynthesizedMembers, + mappedDeletedMembers); + } + + private protected abstract SymbolMatcher CreatePreviousToCurrentSourceAssemblyMatcher( + EmitBaseline previousGeneration, + SynthesizedTypeMaps otherSynthesizedTypes, + IReadOnlyDictionary> otherSynthesizedMembers, + IReadOnlyDictionary> otherDeletedMembers); internal EmitBaseline? SerializeToDeltaStreams( CommonPEModuleBuilder moduleBeingBuilt, diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs index 20e287c2e1069..1548073d4d105 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -103,16 +104,21 @@ private IReadOnlyDictionary MapAddedOrChangedMeth return result; } - private static ImmutableSegmentedDictionary MapAnonymousTypes( - ImmutableSegmentedDictionary previousTypes, - ImmutableSegmentedDictionary newTypes) + /// + /// Merges anonymous types/delegates generated during lowering, or emit, of the current compilation with aggregate + /// types/delegates from all previous source generations (gen >= 1) similarly to + /// + private static ImmutableSegmentedDictionary MapAnonymousTypesAndDelegatesWithUniqueKey( + ImmutableSegmentedDictionary previousTypes, + ImmutableSegmentedDictionary newTypes) + where TKey : IEquatable { if (previousTypes.Count == 0) { return newTypes; } - var builder = ImmutableSegmentedDictionary.CreateBuilder(); + var builder = ImmutableSegmentedDictionary.CreateBuilder(); builder.AddRange(newTypes); foreach (var (key, previousValue) in previousTypes) @@ -128,31 +134,10 @@ private static ImmutableSegmentedDictionary MapAnonymousDelegates( - ImmutableSegmentedDictionary previousDelegates, - ImmutableSegmentedDictionary newDelegates) - { - if (previousDelegates.Count == 0) - { - return newDelegates; - } - - var builder = ImmutableSegmentedDictionary.CreateBuilder(); - builder.AddRange(newDelegates); - - foreach (var (key, previousValue) in previousDelegates) - { - if (newDelegates.ContainsKey(key)) - { - continue; - } - - builder.Add(key, previousValue); - } - - return builder.ToImmutable(); - } - + /// + /// Merges anonymous delegates with indexed names generated during lowering, or emit, of the current compilation with aggregate + /// delegates from all previous source generations (gen >= 1) similarly to + /// private ImmutableSegmentedDictionary> MapAnonymousDelegatesWithIndexedNames( ImmutableSegmentedDictionary> previousDelegates, ImmutableSegmentedDictionary> newDelegates) @@ -201,8 +186,8 @@ private ImmutableSegmentedDictionary new SynthesizedTypeMaps( - MapAnonymousTypes(previousTypes.AnonymousTypes, newTypes.AnonymousTypes), - MapAnonymousDelegates(previousTypes.AnonymousDelegates, newTypes.AnonymousDelegates), + MapAnonymousTypesAndDelegatesWithUniqueKey(previousTypes.AnonymousTypes, newTypes.AnonymousTypes), + MapAnonymousTypesAndDelegatesWithUniqueKey(previousTypes.AnonymousDelegates, newTypes.AnonymousDelegates), MapAnonymousDelegatesWithIndexedNames(previousTypes.AnonymousDelegatesWithIndexedNames, newTypes.AnonymousDelegatesWithIndexedNames)); /// @@ -218,6 +203,8 @@ internal SynthesizedTypeMaps MapSynthesizedTypes(SynthesizedTypeMaps previousTyp /// /// Then the resulting collection shall have the following entries: /// {S' -> {A', B', C, D}, U -> {G, H}, T -> {E, F}} + /// + /// Note that the results may include symbols declared in different compilations (previous generations). /// internal IReadOnlyDictionary> MapSynthesizedOrDeletedMembers( IReadOnlyDictionary> previousMembers, diff --git a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb index a69866bb64c64..46e90c7caa5cd 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb @@ -2534,8 +2534,18 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return True End Function - Private Protected Overrides Function MapToCompilation(moduleBeingBuilt As CommonPEModuleBuilder) As EmitBaseline - Return EmitHelpers.MapToCompilation(Me, DirectCast(moduleBeingBuilt, PEDeltaAssemblyBuilder)) + Private Protected Overrides Function CreatePreviousToCurrentSourceAssemblyMatcher( + previousGeneration As EmitBaseline, + otherSynthesizedTypes As SynthesizedTypeMaps, + otherSynthesizedMembers As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), + otherDeletedMembers As IReadOnlyDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal))) As SymbolMatcher + + Return New VisualBasicSymbolMatcher( + sourceAssembly:=DirectCast(previousGeneration.Compilation, VisualBasicCompilation).SourceAssembly, + otherAssembly:=SourceAssembly, + otherSynthesizedTypes, + otherSynthesizedMembers, + otherDeletedMembers) End Function Friend Overrides Function GenerateResources( diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb index ba74d2fc6ce5d..ae399601162d7 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb @@ -166,54 +166,5 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Return False End Function - - Friend Function MapToCompilation( - compilation As VisualBasicCompilation, - moduleBeingBuilt As PEDeltaAssemblyBuilder) As EmitBaseline - - Dim previousGeneration = moduleBeingBuilt.PreviousGeneration - Debug.Assert(previousGeneration.Compilation IsNot compilation) - - If previousGeneration.Ordinal = 0 Then - ' Initial generation, nothing to map. (Since the initial generation - ' is always loaded from metadata in the context of the current - ' compilation, there's no separate mapping step.) - Return previousGeneration - End If - - Dim currentSynthesizedTypes = moduleBeingBuilt.GetAllSynthesizedTypes() - Dim currentSynthesizedMembers = moduleBeingBuilt.GetAllSynthesizedMembers() - Dim currentDeletedMembers = moduleBeingBuilt.EncSymbolChanges.DeletedMembers - - ' Mapping from previous compilation to the current. - Dim previousSourceAssembly = DirectCast(previousGeneration.Compilation, VisualBasicCompilation).SourceAssembly - - Dim matcher = New VisualBasicSymbolMatcher( - sourceAssembly:=previousSourceAssembly, - otherAssembly:=compilation.SourceAssembly, - otherSynthesizedTypes:=currentSynthesizedTypes, - otherSynthesizedMembers:=currentSynthesizedMembers, - otherDeletedMembers:=currentDeletedMembers) - - Dim mappedSynthesizedTypes = matcher.MapSynthesizedTypes(previousGeneration.SynthesizedTypes, currentSynthesizedTypes) - Dim mappedSynthesizedMembers = matcher.MapSynthesizedOrDeletedMembers(previousGeneration.SynthesizedMembers, currentSynthesizedMembers, isDeletedMemberMapping:=False) - Dim mappedDeletedMembers = matcher.MapSynthesizedOrDeletedMembers(previousGeneration.DeletedMembers, currentDeletedMembers, isDeletedMemberMapping:=True) - - ' TODO can we reuse some data from the previous matcher? - Dim matcherWithAllSynthesizedTypesAndMembers = New VisualBasicSymbolMatcher( - sourceAssembly:=previousSourceAssembly, - otherAssembly:=compilation.SourceAssembly, - otherSynthesizedTypes:=mappedSynthesizedTypes, - otherSynthesizedMembers:=mappedSynthesizedMembers, - otherDeletedMembers:=mappedDeletedMembers) - - Return matcherWithAllSynthesizedTypesAndMembers.MapBaselineToCompilation( - previousGeneration, - compilation, - moduleBeingBuilt, - mappedSynthesizedTypes, - mappedSynthesizedMembers, - mappedDeletedMembers) - End Function End Module End Namespace From c1742d200d418e8192a30bff4b2bc39623fe613a Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Sat, 9 Aug 2025 15:10:34 -0700 Subject: [PATCH 3/4] Fix --- .../Symbols/AnonymousTypes/AnonymousTypeManager_Templates.vb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Compilers/VisualBasic/Portable/Symbols/AnonymousTypes/AnonymousTypeManager_Templates.vb b/src/Compilers/VisualBasic/Portable/Symbols/AnonymousTypes/AnonymousTypeManager_Templates.vb index 409fef44407ff..615ced1480977 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/AnonymousTypes/AnonymousTypeManager_Templates.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/AnonymousTypes/AnonymousTypeManager_Templates.vb @@ -161,11 +161,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End If End Sub - Private Shared Function CreatePlaceholderTypeDescriptor(key As Microsoft.CodeAnalysis.Emit.AnonymousTypeKey) As AnonymousTypeDescriptor - Dim names = key.Fields.SelectAsArray(Function(f) New AnonymousTypeField(f.Name, Location.None, f.IsKey)) - Return New AnonymousTypeDescriptor(names, Location.None, True) - End Function - ''' ''' Resets numbering in anonymous type names and compiles the ''' anonymous type methods. Also seals the collection of templates. From 509aae32e2a877d3b5b3fa536276fb9577c8c9d3 Mon Sep 17 00:00:00 2001 From: tmat Date: Tue, 19 Aug 2025 12:08:16 -0700 Subject: [PATCH 4/4] Feedback --- .../AnonymousTypeManager.Templates.cs | 1 - .../AnonymousTypes/AnonymousTypeManager.cs | 1 - .../Portable/Emit/CommonPEModuleBuilder.cs | 2 +- .../EditAndContinue/SynthesizedTypeMaps.cs | 2 +- .../EditAndContinue/StatementEditingTests.cs | 36 +++++++++++++++++++ .../AbstractEditAndContinueAnalyzer.cs | 16 +++++++-- .../EditAndContinueTest.GenerationVerifier.cs | 1 - 7 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs index 9faba7e831c35..d83cb996d8b05 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs @@ -109,7 +109,6 @@ private void CheckSourceLocationSeen(AnonymousTypePublicSymbol anonymous) #endif } -#nullable enable private ConcurrentDictionary AnonymousTypeTemplates { get diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs index 7614e068639e9..913808058a9d8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Diagnostics; -using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Symbols; namespace Microsoft.CodeAnalysis.CSharp.Symbols diff --git a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs index 77a45f03757e4..958b4a34b1941 100644 --- a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs +++ b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs @@ -1087,7 +1087,7 @@ internal override ImmutableDictionary Compilation.CommonAnonymousTypeManager.GetSynthesizedTypeMaps(); #endregion diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/SynthesizedTypeMaps.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/SynthesizedTypeMaps.cs index ecf353e7da635..3269a53c49b55 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/SynthesizedTypeMaps.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/SynthesizedTypeMaps.cs @@ -32,7 +32,7 @@ public bool IsEmpty /// /// In C#, the set of anonymous delegates with name that is not determined by parameter types only - /// and need to suffixed by an index (e.g. delegates may have same parameter types but differ in default parameter values); + /// and need to be suffixed by an index (e.g. delegates may have same parameter types but differ in default parameter values); /// in VB, this set is unused and empty. /// public ImmutableSegmentedDictionary> AnonymousDelegatesWithIndexedNames { get; } diff --git a/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs index 3f67252c8104a..22913136cdf35 100644 --- a/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -3791,6 +3791,42 @@ void F() ])); } + [Fact] + public void Lambdas_Update_Signature_ParamsArray() + { + var src1 = """ + using System; + + class C + { + void F() + { + var f = (int[] a) => 1; + } + } + """; + var src2 = """ + using System; + + class C + { + void F() + { + var f = (params int[] a) => 1; + } + } + """; + + var edits = GetTopEdits(src1, src2); + var syntaxMap = edits.GetSyntaxMap(); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), syntaxMap, rudeEdits: + [ + RuntimeRudeEdit(0, RudeEditKind.ChangingLambdaParameters, syntaxMap.NodePosition(0), arguments: [GetResource("lambda")]) + ])); + } + [Fact] public void Lambdas_Update_Signature_ParameterRefness1() { diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 564dcf854d977..e03279eaf9c63 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -2547,6 +2547,9 @@ protected static bool ParameterTypesEquivalent(ImmutableArray protected static bool ParameterDefaultValuesEquivalent(ImmutableArray oldParameters, ImmutableArray newParameters) => oldParameters.SequenceEqual(newParameters, ParameterDefaultValuesEquivalent); + protected static bool LambdaParametersEquivalent(ImmutableArray oldParameters, ImmutableArray newParameters) + => oldParameters.SequenceEqual(newParameters, LambdaParameterEquivalent); + protected static bool CustomModifiersEquivalent(CustomModifier oldModifier, CustomModifier newModifier, bool exact) => oldModifier.IsOptional == newModifier.IsOptional && TypesEquivalent(oldModifier.Modifier, newModifier.Modifier, exact); @@ -2589,6 +2592,16 @@ protected static bool ParameterDefaultValuesEquivalent(IParameterSymbol oldParam => oldParameter.HasExplicitDefaultValue == newParameter.HasExplicitDefaultValue && (!oldParameter.HasExplicitDefaultValue || Equals(oldParameter.ExplicitDefaultValue, newParameter.ExplicitDefaultValue)); + /// + /// Lambda parameters are equivallent if the type of the lambda as emitted to IL doesn't change. + /// Tuple element names, dynamic, etc. do not affect lambda natural type. + /// Default values and "params" do. + /// + protected static bool LambdaParameterEquivalent(IParameterSymbol oldParameter, IParameterSymbol newParameter) + => ParameterTypesEquivalent(oldParameter, newParameter, exact: false) && + ParameterDefaultValuesEquivalent(oldParameter, newParameter) && + oldParameter.IsParams == newParameter.IsParams; + protected static bool TypeParameterConstraintsEquivalent(ITypeParameterSymbol oldParameter, ITypeParameterSymbol newParameter, bool exact) => TypesEquivalent(oldParameter.ConstraintTypes, newParameter.ConstraintTypes, exact) && oldParameter.HasReferenceTypeConstraint == newParameter.HasReferenceTypeConstraint && @@ -6578,8 +6591,7 @@ private void ReportLambdaSignatureRudeEdits( var newLambdaSymbol = (IMethodSymbol)diagnosticContext.RequiredNewSymbol; // signature validation: - if (!ParameterTypesEquivalent(oldLambdaSymbol.Parameters, newLambdaSymbol.Parameters, exact: false) || - !ParameterDefaultValuesEquivalent(oldLambdaSymbol.Parameters, newLambdaSymbol.Parameters)) + if (!LambdaParametersEquivalent(oldLambdaSymbol.Parameters, newLambdaSymbol.Parameters)) { // If a delegate type for the lambda is synthesized (anonymous) changing default parameter value changes the synthesized delegate type. // If the delegate type is not synthesized the default value is ignored and warning is reported by the compiler. diff --git a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs index 5ee0a89459dc8..f365d76c3b28a 100644 --- a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs +++ b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; -using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Symbols; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities;