diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs index 5cbd81fde149..372a6645a5c7 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs @@ -10,71 +10,6 @@ namespace System.Text.Json.SourceGeneration.Tests { - [JsonSerializable] - public class Location - { - public int Id { get; set; } - public string Address1 { get; set; } - public string Address2 { get; set; } - public string City { get; set; } - public string State { get; set; } - public string PostalCode { get; set; } - public string Name { get; set; } - public string PhoneNumber { get; set; } - public string Country { get; set; } - } - - [JsonSerializable] - public class ActiveOrUpcomingEvent - { - public int Id { get; set; } - public string ImageUrl { get; set; } - public string Name { get; set; } - public string CampaignName { get; set; } - public string CampaignManagedOrganizerName { get; set; } - public string Description { get; set; } - public DateTimeOffset StartDate { get; set; } - public DateTimeOffset EndDate { get; set; } - } - - [JsonSerializable] - public class CampaignSummaryViewModel - { - public int Id { get; set; } - public string Title { get; set; } - public string Description { get; set; } - public string ImageUrl { get; set; } - public string OrganizationName { get; set; } - public string Headline { get; set; } - } - - [JsonSerializable] - public class IndexViewModel - { - public List ActiveOrUpcomingEvents { get; set; } - public CampaignSummaryViewModel FeaturedCampaign { get; set; } - public bool IsNewAccount { get; set; } - public bool HasFeaturedCampaign => FeaturedCampaign != null; - } - - [JsonSerializable] - public class WeatherForecastWithPOCOs - { - public DateTimeOffset Date { get; set; } - public int TemperatureCelsius { get; set; } - public string Summary { get; set; } - public string SummaryField; - public List DatesAvailable { get; set; } - public Dictionary TemperatureRanges { get; set; } - public string[] SummaryWords { get; set; } - } - - public class HighLowTemps - { - public int High { get; set; } - public int Low { get; set; } - } - public static class JsonSerializerSourceGeneratorTests { [Fact] @@ -82,6 +17,8 @@ public static void RoundTripLocation() { Location expected = CreateLocation(); + // Location is renamed to SystemTextJsonSourceGenerationTestsLocation given there is another type with the name Location. + // Warning to the user is displayed with this detailed at compile time. string json = JsonSerializer.Serialize(expected, JsonContext.Instance.SystemTextJsonSourceGenerationTestsLocation); Location obj = JsonSerializer.Deserialize(json, JsonContext.Instance.SystemTextJsonSourceGenerationTestsLocation); @@ -93,8 +30,8 @@ public static void RoundTripIndexViewModel() { IndexViewModel expected = CreateIndexViewModel(); - string json = JsonSerializer.Serialize(expected, JsonContext.Instance.SystemTextJsonSourceGenerationTestsIndexViewModel); - IndexViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Instance.SystemTextJsonSourceGenerationTestsIndexViewModel); + string json = JsonSerializer.Serialize(expected, JsonContext.Instance.IndexViewModel); + IndexViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Instance.IndexViewModel); VerifyIndexViewModel(expected, obj); } @@ -104,8 +41,8 @@ public static void RoundTripCampaignSummaryViewModel() { CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); - string json = JsonSerializer.Serialize(expected, JsonContext.Instance.SystemTextJsonSourceGenerationTestsCampaignSummaryViewModel); - CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Instance.SystemTextJsonSourceGenerationTestsCampaignSummaryViewModel); + string json = JsonSerializer.Serialize(expected, JsonContext.Instance.CampaignSummaryViewModel); + CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Instance.CampaignSummaryViewModel); VerifyCampaignSummaryViewModel(expected, obj); } @@ -115,8 +52,8 @@ public static void RoundTripActiveOrUpcomingEvent() { ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); - string json = JsonSerializer.Serialize(expected, JsonContext.Instance.SystemTextJsonSourceGenerationTestsActiveOrUpcomingEvent); - ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, JsonContext.Instance.SystemTextJsonSourceGenerationTestsActiveOrUpcomingEvent); + string json = JsonSerializer.Serialize(expected, JsonContext.Instance.ActiveOrUpcomingEvent); + ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, JsonContext.Instance.ActiveOrUpcomingEvent); VerifyActiveOrUpcomingEvent(expected, obj); } @@ -126,12 +63,23 @@ public static void RoundTripCollectionsDictionary() { WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); - string json = JsonSerializer.Serialize(expected, JsonContext.Instance.SystemTextJsonSourceGenerationTestsWeatherForecastWithPOCOs); - WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, JsonContext.Instance.SystemTextJsonSourceGenerationTestsWeatherForecastWithPOCOs); + string json = JsonSerializer.Serialize(expected, JsonContext.Instance.WeatherForecastWithPOCOs); + WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, JsonContext.Instance.WeatherForecastWithPOCOs); VerifyWeatherForecastWithPOCOs(expected, obj); } + [Fact] + public static void RoundTripTypeNameClash() + { + RepeatedTypes.Location expected = CreateRepeatedLocation(); + + string json = JsonSerializer.Serialize(expected, JsonContext.Instance.Location); + RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, JsonContext.Instance.Location); + + VerifyRepeatedLocation(expected, obj); + } + internal static Location CreateLocation() { return new Location @@ -312,5 +260,32 @@ internal static void VerifyWeatherForecastWithPOCOs(WeatherForecastWithPOCOs exp Assert.Equal(expected.SummaryWords[i], obj.SummaryWords[i]); } } + + internal static RepeatedTypes.Location CreateRepeatedLocation() + { + return new RepeatedTypes.Location + { + FakeId = 1234, + FakeAddress1 = "The Street Name", + FakeAddress2 = "20/11", + FakeCity = "The City", + FakeState = "The State", + FakePostalCode = "abc-12", + FakeName = "Nonexisting", + FakePhoneNumber = "+0 11 222 333 44", + FakeCountry = "The Greatest" + }; + } + internal static void VerifyRepeatedLocation(RepeatedTypes.Location expected, RepeatedTypes.Location obj) + { + Assert.Equal(expected.FakeAddress1, obj.FakeAddress1); + Assert.Equal(expected.FakeAddress2, obj.FakeAddress2); + Assert.Equal(expected.FakeCity, obj.FakeCity); + Assert.Equal(expected.FakeState, obj.FakeState); + Assert.Equal(expected.FakePostalCode, obj.FakePostalCode); + Assert.Equal(expected.FakeName, obj.FakeName); + Assert.Equal(expected.FakePhoneNumber, obj.FakePhoneNumber); + Assert.Equal(expected.FakeCountry, obj.FakeCountry); + } } } diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj index 1fec7debe646..ddbbc58900ed 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/TestClasses.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/TestClasses.cs new file mode 100644 index 000000000000..412772a914fd --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/TestClasses.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.Json.Serialization; + +namespace System.Text.Json.SourceGeneration.Tests.RepeatedTypes +{ + [JsonSerializable] + public class Location + { + public int FakeId { get; set; } + public string FakeAddress1 { get; set; } + public string FakeAddress2 { get; set; } + public string FakeCity { get; set; } + public string FakeState { get; set; } + public string FakePostalCode { get; set; } + public string FakeName { get; set; } + public string FakePhoneNumber { get; set; } + public string FakeCountry { get; set; } + } +} + +namespace System.Text.Json.SourceGeneration.Tests +{ + [JsonSerializable] + public class Location + { + public int Id { get; set; } + public string Address1 { get; set; } + public string Address2 { get; set; } + public string City { get; set; } + public string State { get; set; } + public string PostalCode { get; set; } + public string Name { get; set; } + public string PhoneNumber { get; set; } + public string Country { get; set; } + } + + [JsonSerializable] + public class ActiveOrUpcomingEvent + { + public int Id { get; set; } + public string ImageUrl { get; set; } + public string Name { get; set; } + public string CampaignName { get; set; } + public string CampaignManagedOrganizerName { get; set; } + public string Description { get; set; } + public DateTimeOffset StartDate { get; set; } + public DateTimeOffset EndDate { get; set; } + } + + [JsonSerializable] + public class CampaignSummaryViewModel + { + public int Id { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public string ImageUrl { get; set; } + public string OrganizationName { get; set; } + public string Headline { get; set; } + } + + [JsonSerializable] + public class IndexViewModel + { + public List ActiveOrUpcomingEvents { get; set; } + public CampaignSummaryViewModel FeaturedCampaign { get; set; } + public bool IsNewAccount { get; set; } + public bool HasFeaturedCampaign => FeaturedCampaign != null; + } + + [JsonSerializable] + public class WeatherForecastWithPOCOs + { + public DateTimeOffset Date { get; set; } + public int TemperatureCelsius { get; set; } + public string Summary { get; set; } + public string SummaryField; + public List DatesAvailable { get; set; } + public Dictionary TemperatureRanges { get; set; } + public string[] SummaryWords { get; set; } + } + + public class HighLowTemps + { + public int High { get; set; } + public int Low { get; set; } + } + +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs index 4b986e515f4d..3cda4ce17487 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs @@ -117,6 +117,7 @@ public class CampaignSummaryViewModel public static Compilation CreateActiveOrUpcomingEventCompilation() { string source = @" + using System; namespace ReferencedAssembly { public class ActiveOrUpcomingEvent @@ -149,5 +150,50 @@ public class HighLowTemps return CreateCompilation(source); } + + public static Compilation CreateRepeatedLocationsCompilation() + { + string source = @" + using System; + using System.Collections; + using System.Collections.Generic; + using System.Text.Json.Serialization; + + namespace Fake + { + [JsonSerializable] + public class Location + { + public int FakeId { get; set; } + public string FakeAddress1 { get; set; } + public string FakeAddress2 { get; set; } + public string FakeCity { get; set; } + public string FakeState { get; set; } + public string FakePostalCode { get; set; } + public string FakeName { get; set; } + public string FakePhoneNumber { get; set; } + public string FakeCountry { get; set; } + } + } + + namespace HelloWorld + { + [JsonSerializable] + public class Location + { + public int Id { get; set; } + public string Address1 { get; set; } + public string Address2 { get; set; } + public string City { get; set; } + public string State { get; set; } + public string PostalCode { get; set; } + public string Name { get; set; } + public string PhoneNumber { get; set; } + public string Country { get; set; } + } + }"; + + return CreateCompilation(source); + } } } diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorDiagnosticsTests.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorDiagnosticsTests.cs index 97388207db3a..c64bcabacf8f 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorDiagnosticsTests.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorDiagnosticsTests.cs @@ -3,10 +3,10 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Xunit; -using Xunit.Abstractions; namespace System.Text.Json.SourceGeneration.UnitTests { @@ -17,7 +17,7 @@ public void SuccessfulSourceGeneration() { // Compile the referenced assembly first. Compilation campaignCompilation = CompilationHelper.CreateCampaignSummaryViewModelCompilation(); - Compilation eventCompilation = CompilationHelper.CreateCampaignSummaryViewModelCompilation(); + Compilation eventCompilation = CompilationHelper.CreateActiveOrUpcomingEventCompilation(); // Emit the image of the referenced assembly. byte[] campaignImage = CompilationHelper.CreateAssemblyImage(campaignCompilation); @@ -25,7 +25,9 @@ public void SuccessfulSourceGeneration() // Main source for current compilation. string source = @" + using System.Collections.Generic; using System.Text.Json.Serialization; + using ReferencedAssembly; namespace JsonSourceGenerator { @@ -52,9 +54,9 @@ public class IndexViewModel // Expected info logs. string[] expectedInfoDiagnostics = new string[] { - "Generated type class TestAssemblyActiveOrUpcomingEvent for root type IndexViewModel", - "Generated type class TestAssemblyCampaignSummaryViewModel for root type IndexViewModel", - "Generated type class TestAssemblyIndexViewModel for root type IndexViewModel", + "Generated type class ActiveOrUpcomingEvent for root type IndexViewModel", + "Generated type class CampaignSummaryViewModel for root type IndexViewModel", + "Generated type class IndexViewModel for root type IndexViewModel", }; CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, expectedInfoDiagnostics); @@ -67,7 +69,7 @@ public void UnSuccessfulSourceGeneration() { // Compile the referenced assembly first. Compilation campaignCompilation = CompilationHelper.CreateCampaignSummaryViewModelCompilation(); - Compilation eventCompilation = CompilationHelper.CreateCampaignSummaryViewModelCompilation(); + Compilation eventCompilation = CompilationHelper.CreateActiveOrUpcomingEventCompilation(); // Emit the image of the referenced assembly. byte[] campaignImage = CompilationHelper.CreateAssemblyImage(campaignCompilation); @@ -77,6 +79,7 @@ public void UnSuccessfulSourceGeneration() string source = @" using System.Text.Json.Serialization; using System.Collections.Generic; + using ReferencedAssembly; namespace JsonSourceGenerator { @@ -109,6 +112,28 @@ public class IndexViewModel CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, new string[] { }); } + [Fact] + public void NameClashSourceGeneration() + { + Compilation compilation = CompilationHelper.CreateRepeatedLocationsCompilation(); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + + // Expected info logs. + string[] expectedInfoDiagnostics = new string[] { + "Generated type class Location for root type Location", + "Generated type class TestAssemblyLocation for root type Location", + }; + // Expected warning logs. + string[] expectedWarningDiagnostics = new string[] { "Changed name type Location to TestAssemblyLocation. To use please call `JsonContext.Instance.TestAssemblyLocation`" }; + + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, expectedInfoDiagnostics); + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, expectedWarningDiagnostics); + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, new string[] { }); + } + private void CheckDiagnosticMessages(ImmutableArray diagnostics, DiagnosticSeverity level, string[] expectedMessages) { Assert.Equal(expectedMessages, diagnostics.Where(diagnostic => diagnostic.Severity == level ).Select(diagnostic => diagnostic.GetMessage()).ToArray()); diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs index b4d966e01c06..b4bf85b7e3ae 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs @@ -56,14 +56,14 @@ public void UsePrivates() // Check base functionality of found types. Assert.Equal(1, generator.FoundTypes.Count); - Type myType = generator.FoundTypes["MyType"]; + Type myType = generator.FoundTypes["HelloWorld.MyType"]; Assert.Equal("HelloWorld.MyType", myType.FullName); // Check for received fields, properties and methods in created type. string[] expectedPropertyNames = { "PublicPropertyInt", "PublicPropertyString",}; string[] expectedFieldNames = { "PublicChar", "PublicDouble" }; string[] expectedMethodNames = { "get_PrivatePropertyInt", "get_PrivatePropertyString", "get_PublicPropertyInt", "get_PublicPropertyString", "MyMethod", "MySecondMethod", "set_PrivatePropertyInt", "set_PrivatePropertyString", "set_PublicPropertyInt", "set_PublicPropertyString", "UsePrivates" }; - CheckFieldsPropertiesMethods("MyType", ref generator, expectedFieldNames, expectedPropertyNames, expectedMethodNames); + CheckFieldsPropertiesMethods("HelloWorld.MyType", ref generator, expectedFieldNames, expectedPropertyNames, expectedMethodNames); } [Fact] @@ -122,8 +122,8 @@ public class NotMyType { } // Check base functionality of found types. Assert.Equal(2, generator.FoundTypes.Count); - Type myType = generator.FoundTypes["MyType"]; - Type notMyType = generator.FoundTypes["NotMyType"]; + Type myType = generator.FoundTypes["HelloWorld.MyType"]; + Type notMyType = generator.FoundTypes["ReferencedAssembly.Location"]; // Check for MyType. Assert.Equal("HelloWorld.MyType", myType.FullName); @@ -132,17 +132,17 @@ public class NotMyType { } string[] expectedFieldNamesMyType = { "PublicChar", "PublicDouble" }; string[] expectedPropertyNamesMyType = { "PublicPropertyInt", "PublicPropertyString" }; string[] expectedMethodNamesMyType = { "get_PrivatePropertyInt", "get_PrivatePropertyString", "get_PublicPropertyInt", "get_PublicPropertyString", "MyMethod", "MySecondMethod", "set_PrivatePropertyInt", "set_PrivatePropertyString", "set_PublicPropertyInt", "set_PublicPropertyString", "UsePrivates" }; - CheckFieldsPropertiesMethods("MyType", ref generator, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType); + CheckFieldsPropertiesMethods("HelloWorld.MyType", ref generator, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType); // Check for NotMyType. - Assert.Equal("ReferencedAssembly.Location", generator.FoundTypes["NotMyType"].FullName); + Assert.Equal("ReferencedAssembly.Location", notMyType.FullName); // Check for received fields, properties and methods for NotMyType. string[] expectedFieldNamesNotMyType = { }; string[] expectedPropertyNamesNotMyType = { "Address1", "Address2", "City", "Country", "Id", "Name", "PhoneNumber", "PostalCode", "State" }; string[] expectedMethodNamesNotMyType = { "get_Address1", "get_Address2", "get_City", "get_Country", "get_Id", "get_Name", "get_PhoneNumber", "get_PostalCode", "get_State", "set_Address1", "set_Address2", "set_City", "set_Country", "set_Id", "set_Name", "set_PhoneNumber", "set_PostalCode", "set_State" }; - CheckFieldsPropertiesMethods("NotMyType", ref generator, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType); + CheckFieldsPropertiesMethods("ReferencedAssembly.Location", ref generator, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType); } [Fact] @@ -209,23 +209,37 @@ public class ShouldNotFind { } Assert.Equal(2, generator.FoundTypes.Count); // Check for MyType. - Assert.Equal("HelloWorld.MyType", generator.FoundTypes["MyType"].FullName); + Assert.Equal("HelloWorld.MyType", generator.FoundTypes["HelloWorld.MyType"].FullName); // Check for received fields, properties and methods for MyType. string[] expectedFieldNamesMyType = { "PublicChar", "PublicDouble" }; string[] expectedPropertyNamesMyType = { "PublicPropertyInt", "PublicPropertyString" }; string[] expectedMethodNamesMyType = { "get_PrivatePropertyInt", "get_PrivatePropertyString", "get_PublicPropertyInt", "get_PublicPropertyString", "MyMethod", "MySecondMethod", "set_PrivatePropertyInt", "set_PrivatePropertyString", "set_PublicPropertyInt", "set_PublicPropertyString", "UsePrivates" }; - CheckFieldsPropertiesMethods("MyType", ref generator, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType); + CheckFieldsPropertiesMethods("HelloWorld.MyType", ref generator, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType); // Check for NotMyType. - Assert.Equal("ReferencedAssembly.Location", generator.FoundTypes["NotMyType"].FullName); + Assert.Equal("ReferencedAssembly.Location", generator.FoundTypes["ReferencedAssembly.Location"].FullName); // Check for received fields, properties and methods for NotMyType. string[] expectedFieldNamesNotMyType = { }; string[] expectedPropertyNamesNotMyType = { "Address1", "Address2", "City", "Country", "Id", "Name", "PhoneNumber", "PostalCode", "State" }; string[] expectedMethodNamesNotMyType = { "get_Address1", "get_Address2", "get_City", "get_Country", "get_Id", "get_Name", "get_PhoneNumber", "get_PostalCode", "get_State", "set_Address1", "set_Address2", "set_City", "set_Country", "set_Id", "set_Name", "set_PhoneNumber", "set_PostalCode", "set_State" }; - CheckFieldsPropertiesMethods("NotMyType", ref generator, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType ); + CheckFieldsPropertiesMethods("ReferencedAssembly.Location", ref generator, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType ); + } + + [Fact] + public void NameClashCompilation() + { + Compilation compilation = CompilationHelper.CreateRepeatedLocationsCompilation(); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + + // Make sure compilation was successful. + CheckCompilationDiagnosticsErrors(generatorDiags); + CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics()); } [Fact] diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs index ee3b2e7cf43d..fc0e48946dbd 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs @@ -59,12 +59,12 @@ public void Execute(SourceGeneratorContext context) // Get non-user owned typeSymbol from IdentifierNameSyntax and add to found types. INamedTypeSymbol externalTypeSymbol = model.GetTypeInfo(externalTypeNode).ConvertedType as INamedTypeSymbol; - FoundTypes[typeDeclarationNode.Identifier.Text] = new TypeWrapper(externalTypeSymbol, metadataLoadContext); + FoundTypes[externalTypeSymbol.ToString()] = new TypeWrapper(externalTypeSymbol, metadataLoadContext); } else { // Add user owned type into found types. - FoundTypes[typeDeclarationNode.Identifier.Text] = new TypeWrapper(typeSymbol, metadataLoadContext); + FoundTypes[typeSymbol.ToString()] = new TypeWrapper(typeSymbol, metadataLoadContext); } } } @@ -81,9 +81,9 @@ public void Execute(SourceGeneratorContext context) } // Generate sources for each type. - foreach (KeyValuePair entry in codegen.Types) + foreach (KeyValuePair> entry in codegen.Types) { - context.AddSource($"{entry.Key.Name}ClassInfo.g.cs", SourceText.From(entry.Value, Encoding.UTF8)); + context.AddSource($"{entry.Value.Item1}ClassInfo.g.cs", SourceText.From(entry.Value.Item2, Encoding.UTF8)); } // For each diagnostic, report to the user. diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGeneratorDiagnostics.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGeneratorDiagnostics.cs index 15185fecc9ac..36e22b366e4a 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGeneratorDiagnostics.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGeneratorDiagnostics.cs @@ -13,6 +13,7 @@ internal sealed partial class JsonSourceGeneratorHelper private DiagnosticDescriptor _failedToGenerateTypeClass; private DiagnosticDescriptor _failedToAddNewTypesFromMembers; private DiagnosticDescriptor _notSupported; + private DiagnosticDescriptor _typeNameClash; private void InitializeDiagnosticDescriptors() { @@ -24,6 +25,7 @@ private void InitializeDiagnosticDescriptors() "category", DiagnosticSeverity.Info, isEnabledByDefault: true); + _failedToGenerateTypeClass = new DiagnosticDescriptor( "JsonSourceGeneration", @@ -33,6 +35,7 @@ private void InitializeDiagnosticDescriptors() DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "Error message: {2}"); + _failedToAddNewTypesFromMembers = new DiagnosticDescriptor( "JsonSourceGeneration", @@ -41,6 +44,7 @@ private void InitializeDiagnosticDescriptors() "category", DiagnosticSeverity.Warning, isEnabledByDefault: true); + _notSupported = new DiagnosticDescriptor( "JsonSourceGeneration", @@ -49,6 +53,15 @@ private void InitializeDiagnosticDescriptors() "category", DiagnosticSeverity.Warning, isEnabledByDefault: true); + + _typeNameClash = + new DiagnosticDescriptor( + "JsonSourceGeneration", + "Current type name clashes with another source generated type", + "Changed name type {0} to {1}. To use please call `JsonContext.Instance.{1}`", + "category", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); } } } diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGeneratorHelper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGeneratorHelper.cs index a936a7628ac1..352c52a93436 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGeneratorHelper.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGeneratorHelper.cs @@ -27,26 +27,29 @@ internal sealed partial class JsonSourceGeneratorHelper // Generation namespace for source generation code. const string GenerationNamespace = "JsonCodeGeneration"; - // Type for key and a generated-source for value. - public Dictionary Types { get; } + // TypeWrapper for key and for value. + public Dictionary> Types { get; } // Contains types that failed to be generated. private HashSet _failedTypes = new HashSet(); + // Contains used typeinfo identifiers. + private HashSet _usedTypeInfoIdentifiers = new HashSet(); + // Contains list of diagnostics for the code generator. public List Diagnostics { get; } public JsonSourceGeneratorHelper() { // Initialize auto properties. - Types = new Dictionary(); + Types = new Dictionary>(); Diagnostics = new List(); // Initiate diagnostic descriptors. InitializeDiagnosticDescriptors(); } - public struct GenerationClassFrame + public class GenerationClassFrame { public Type RootType; public Type CurrentType; @@ -58,15 +61,21 @@ public struct GenerationClassFrame public bool IsSuccessful; - public GenerationClassFrame(Type rootType, Type currentType) + public GenerationClassFrame(Type rootType, Type currentType, HashSet usedTypeInfoIdentifiers) { RootType = rootType; CurrentType = currentType; - ClassName = currentType.GetCompilableUniqueName(); Source = new StringBuilder(); Properties = CurrentType.GetProperties(); Fields = CurrentType.GetFields(); IsSuccessful = true; + + // If typename was already used, use unique name instead. + ClassName = usedTypeInfoIdentifiers.Contains(currentType.Name) ? + currentType.GetCompilableUniqueName() : currentType.Name; + + // Register new ClassName. + usedTypeInfoIdentifiers.Add(ClassName); } } @@ -99,10 +108,10 @@ public static JsonContext Instance } // Generates metadata for type and returns if it was successful. - private bool GenerateClassInfo(GenerationClassFrame currentFrame, HashSet seenTypes) + private bool GenerateClassInfo(GenerationClassFrame currentFrame, Dictionary seenTypes) { - // Add current type to generated types. - seenTypes.Add(currentFrame.CurrentType); + // Add current type to seen types along with its className.. + seenTypes[currentFrame.CurrentType] = currentFrame.ClassName; // Try to recursively generate necessary field and property types. foreach (FieldInfo field in currentFrame.Fields) @@ -137,7 +146,7 @@ private bool GenerateClassInfo(GenerationClassFrame currentFrame, HashSet InitializeTypeClass(currentFrame); TypeInfoGetterSetter(currentFrame); currentFrame.IsSuccessful &= InitializeTypeInfoProperties(currentFrame); - currentFrame.IsSuccessful &= GenerateTypeInfoConstructor(currentFrame); + currentFrame.IsSuccessful &= GenerateTypeInfoConstructor(currentFrame, seenTypes); GenerateCreateObject(currentFrame); GenerateSerialize(currentFrame); GenerateDeserialize(currentFrame); @@ -148,7 +157,12 @@ private bool GenerateClassInfo(GenerationClassFrame currentFrame, HashSet Diagnostics.Add(Diagnostic.Create(_generatedTypeClass, Location.None, new string[] { currentFrame.RootType.Name, currentFrame.ClassName })); // Add generated typeinfo for current traversal. - Types.Add(currentFrame.CurrentType, currentFrame.Source.ToString()); + Types.Add(currentFrame.CurrentType, new Tuple(currentFrame.ClassName, currentFrame.Source.ToString())); + // If added type had its typeinfo name changed, report to the user. + if (currentFrame.CurrentType.Name != currentFrame.ClassName) + { + Diagnostics.Add(Diagnostic.Create(_typeNameClash, Location.None, new string[] { currentFrame.CurrentType.Name, currentFrame.ClassName })); + } } else { @@ -157,18 +171,21 @@ private bool GenerateClassInfo(GenerationClassFrame currentFrame, HashSet // If not successful remove it from found types hashset and add to failed types list. seenTypes.Remove(currentFrame.CurrentType); _failedTypes.Add(currentFrame.CurrentType); + + // Unregister typeinfo identifier since typeinfo will not be saved. + _usedTypeInfoIdentifiers.Remove(currentFrame.ClassName); } return currentFrame.IsSuccessful; } // Call recursive type generation if unseen type and check for success and cycles. - void GenerateForMembers(GenerationClassFrame currentFrame, Type newType, HashSet seenTypes) + void GenerateForMembers(GenerationClassFrame currentFrame, Type newType, Dictionary seenTypes) { // If new type, recurse. if (IsNewType(newType, seenTypes)) { - bool isMemberSuccessful = GenerateClassInfo(new GenerationClassFrame(currentFrame.RootType, newType), seenTypes); + bool isMemberSuccessful = GenerateClassInfo(new GenerationClassFrame(currentFrame.RootType, newType, _usedTypeInfoIdentifiers), seenTypes); currentFrame.IsSuccessful &= isMemberSuccessful; if (!isMemberSuccessful) @@ -200,8 +217,8 @@ private bool IsSupportedType(Type type) // Returns name of types traversed that can be looked up in the dictionary. public void GenerateClassInfo(Type type) { - HashSet foundTypes = new HashSet(); - GenerateClassInfo(new GenerationClassFrame(rootType: type, currentType: type), foundTypes); + Dictionary foundTypes = new Dictionary(); + GenerateClassInfo(new GenerationClassFrame(rootType: type, currentType: type, _usedTypeInfoIdentifiers), foundTypes); } private Type[] GetTypesToGenerate(Type type) @@ -218,11 +235,25 @@ private Type[] GetTypesToGenerate(Type type) return new Type[] { type }; } - private bool IsNewType(Type type, HashSet foundTypes) => ( + private bool IsNewType(Type type, Dictionary foundTypes) => ( !Types.ContainsKey(type) && - !foundTypes.Contains(type) && + !foundTypes.ContainsKey(type) && !s_simpleTypes.Contains(type)); + private string GetTypeInfoIdentifier(Type type, Dictionary seenTypes) + { + if (s_simpleTypes.Contains(type)) + { + return type.Name; + } + if (Types.ContainsKey(type)) + { + return Types[type].Item1; + } + + return seenTypes[type]; + } + private void AddImportsToTypeClass(GenerationClassFrame currentFrame) { HashSet imports = new HashSet(); @@ -352,7 +383,7 @@ private bool InitializeTypeInfoProperties(GenerationClassFrame currentFrame) return true; } - private bool GenerateTypeInfoConstructor(GenerationClassFrame currentFrame) + private bool GenerateTypeInfoConstructor(GenerationClassFrame currentFrame, Dictionary seenTypes) { Type currentType = currentFrame.CurrentType; @@ -366,33 +397,29 @@ private bool GenerateTypeInfoConstructor(GenerationClassFrame currentFrame) Type[] genericTypes; Type propertyType; string typeClassInfoCall; + string typeInfoIdentifier; foreach (PropertyInfo property in currentFrame.Properties) { propertyType = property.PropertyType; genericTypes = GetTypesToGenerate(propertyType); - // Check if Array. if (propertyType.IsArray) { - typeClassInfoCall = $"KnownCollectionTypeInfos<{genericTypes[0].FullName}>.GetArray(context.{genericTypes[0].GetCompilableUniqueName()}, context)"; + typeInfoIdentifier = GetTypeInfoIdentifier(genericTypes[0], seenTypes); + typeClassInfoCall = $"KnownCollectionTypeInfos<{genericTypes[0].FullName}>.GetArray(context.{typeInfoIdentifier}, context)"; } - else - { - // Default classtype for values. - typeClassInfoCall = $"context.{propertyType.GetCompilableUniqueName()}"; - } - - // Check if IEnumerable. - if (propertyType.IsIEnumerable()) + else if (propertyType.IsIEnumerable()) { if (propertyType.IsIList()) { - typeClassInfoCall = $"KnownCollectionTypeInfos<{genericTypes[0].FullName}>.GetList(context.{genericTypes[0].GetCompilableUniqueName()}, context)"; + typeInfoIdentifier = GetTypeInfoIdentifier(genericTypes[0], seenTypes); + typeClassInfoCall = $"KnownCollectionTypeInfos<{genericTypes[0].FullName}>.GetList(context.{typeInfoIdentifier}, context)"; } else if (propertyType.IsIDictionary()) { - typeClassInfoCall = $"KnownDictionaryTypeInfos<{genericTypes[0].FullName}, {genericTypes[1].FullName}>.GetDictionary(context.{genericTypes[1].GetCompilableUniqueName()}, context)"; + typeInfoIdentifier = GetTypeInfoIdentifier(genericTypes[1], seenTypes); + typeClassInfoCall = $"KnownDictionaryTypeInfos<{genericTypes[0].FullName}, {genericTypes[1].FullName}>.GetDictionary(context.{typeInfoIdentifier}, context)"; } else { @@ -400,6 +427,13 @@ private bool GenerateTypeInfoConstructor(GenerationClassFrame currentFrame) return false; } } + else + { + // Default classtype for values. + typeInfoIdentifier = GetTypeInfoIdentifier(propertyType, seenTypes); + typeClassInfoCall = $"context.{typeInfoIdentifier}"; + } + currentFrame.Source.Append($@" _property_{property.Name} = typeInfo.AddProperty(nameof({currentType.FullName}.{property.Name}),