Fix JSON src-gen errors when context types are not in namespaces (#57183)
authorLayomi Akinrinade <laakinri@microsoft.com>
Wed, 11 Aug 2021 23:38:40 +0000 (16:38 -0700)
committerGitHub <noreply@github.com>
Wed, 11 Aug 2021 23:38:40 +0000 (16:38 -0700)
src/libraries/System.Text.Json/gen/JsonConstants.cs [new file with mode: 0644]
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs
src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs
src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs

diff --git a/src/libraries/System.Text.Json/gen/JsonConstants.cs b/src/libraries/System.Text.Json/gen/JsonConstants.cs
new file mode 100644 (file)
index 0000000..148ffae
--- /dev/null
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Text.Json
+{
+    internal static partial class JsonConstants
+    {
+        public const string GlobalNamespaceValue = "<global namespace>";
+
+        public const string SystemTextJsonSourceGenerationName = "System.Text.Json.SourceGeneration";
+
+        public const string IJsonOnSerializedFullName = "System.Text.Json.Serialization.IJsonOnSerialized";
+        public const string IJsonOnSerializingFullName = "System.Text.Json.Serialization.IJsonOnSerializing";
+    }
+}
index 118a113..2526edd 100644 (file)
@@ -66,7 +66,7 @@ namespace System.Text.Json.SourceGeneration
                 id: "SYSLIB1030",
                 title: new LocalizableResourceString(nameof(SR.TypeNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
                 messageFormat: new LocalizableResourceString(nameof(SR.TypeNotSupportedMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
-                category: SystemTextJsonSourceGenerationName,
+                category: JsonConstants.SystemTextJsonSourceGenerationName,
                 defaultSeverity: DiagnosticSeverity.Warning,
                 isEnabledByDefault: true);
 
@@ -74,7 +74,7 @@ namespace System.Text.Json.SourceGeneration
                 id: "SYSLIB1031",
                 title: new LocalizableResourceString(nameof(SR.DuplicateTypeNameTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
                 messageFormat: new LocalizableResourceString(nameof(SR.DuplicateTypeNameMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
-                category: SystemTextJsonSourceGenerationName,
+                category: JsonConstants.SystemTextJsonSourceGenerationName,
                 defaultSeverity: DiagnosticSeverity.Warning,
                 isEnabledByDefault: true);
 
@@ -122,12 +122,18 @@ namespace System.Text.Json.SourceGeneration
                 int declarationCount = declarationList.Count;
                 Debug.Assert(declarationCount >= 1);
 
-                StringBuilder sb = new();
+                string @namespace = _currentContext.ContextType.Namespace;
+                bool isInGlobalNamespace = @namespace == JsonConstants.GlobalNamespaceValue;
+
+                StringBuilder sb = new("// <auto-generated/>");
 
-                sb.Append($@"// <auto-generated/>
+                if (!isInGlobalNamespace)
+                {
+                    sb.Append(@$"
 
-namespace {_currentContext.ContextType.Namespace}
+namespace {@namespace}
 {{");
+                }
 
                 for (int i = 0; i < declarationCount - 1; i++)
                 {
@@ -153,8 +159,10 @@ namespace {_currentContext.ContextType.Namespace}
                     sb.AppendLine(IndentSource("}", numIndentations: declarationCount + i + 1));
                 }
 
-                // Match curly brace for namespace.
-                sb.AppendLine("}");
+                if (!isInGlobalNamespace)
+                {
+                    sb.AppendLine("}");
+                }
 
                 _executionContext.AddSource(fileName, SourceText.From(sb.ToString(), Encoding.UTF8));
             }
@@ -775,7 +783,7 @@ private static {JsonParameterInfoValuesTypeRef}[] {typeGenerationSpec.TypeInfoPr
                 // Begin method logic.
                 if (typeGenSpec.ImplementsIJsonOnSerializing)
                 {
-                    sb.Append($@"(({IJsonOnSerializingFullName}){ValueVarName}).OnSerializing();");
+                    sb.Append($@"((global::{JsonConstants.IJsonOnSerializingFullName}){ValueVarName}).OnSerializing();");
                     sb.Append($@"{Environment.NewLine}    ");
                 }
 
@@ -853,7 +861,7 @@ private static {JsonParameterInfoValuesTypeRef}[] {typeGenerationSpec.TypeInfoPr
                 if (typeGenSpec.ImplementsIJsonOnSerialized)
                 {
                     sb.Append($@"{Environment.NewLine}    ");
-                    sb.Append($@"(({IJsonOnSerializedFullName}){ValueVarName}).OnSerialized();");
+                    sb.Append($@"((global::{JsonConstants.IJsonOnSerializedFullName}){ValueVarName}).OnSerialized();");
                 };
 
                 return GenerateFastPathFuncForType(serializeMethodName, typeRef, sb.ToString(), typeGenSpec.CanBeNull);
index f761410..33a6056 100644 (file)
@@ -84,7 +84,7 @@ namespace System.Text.Json.SourceGeneration
                 id: "SYSLIB1032",
                 title: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
                 messageFormat: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
-                category: SystemTextJsonSourceGenerationName,
+                category: JsonConstants.SystemTextJsonSourceGenerationName,
                 defaultSeverity: DiagnosticSeverity.Warning,
                 isEnabledByDefault: true);
 
@@ -92,7 +92,7 @@ namespace System.Text.Json.SourceGeneration
                 id: "SYSLIB1033",
                 title: new LocalizableResourceString(nameof(SR.MultipleJsonConstructorAttributeTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
                 messageFormat: new LocalizableResourceString(nameof(SR.MultipleJsonConstructorAttributeFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
-                category: SystemTextJsonSourceGenerationName,
+                category: JsonConstants.SystemTextJsonSourceGenerationName,
                 defaultSeverity: DiagnosticSeverity.Error,
                 isEnabledByDefault: true);
 
@@ -385,14 +385,6 @@ namespace System.Text.Json.SourceGeneration
                 }
 
                 Type type = typeSymbol.AsType(_metadataLoadContext);
-                if (type.Namespace == "<global namespace>")
-                {
-                    // typeof() reference where the type's name isn't fully qualified.
-                    // The compilation is not valid and the user needs to fix their code.
-                    // The compiler will notify the user so we don't have to.
-                    return null;
-                }
-
                 TypeGenerationSpec typeGenerationSpec = GetOrAddTypeGenerationSpec(type, generationMode);
 
                 if (typeInfoPropertyName != null)
@@ -775,8 +767,8 @@ namespace System.Text.Json.SourceGeneration
 
                         // GetInterface() is currently not implemented, so we use GetInterfaces().
                         IEnumerable<string> interfaces = type.GetInterfaces().Select(interfaceType => interfaceType.FullName!);
-                        implementsIJsonOnSerialized = interfaces.FirstOrDefault(interfaceName => interfaceName == IJsonOnSerializedFullName) != null;
-                        implementsIJsonOnSerializing = interfaces.FirstOrDefault(interfaceName => interfaceName == IJsonOnSerializingFullName) != null;
+                        implementsIJsonOnSerialized = interfaces.FirstOrDefault(interfaceName => interfaceName == JsonConstants.IJsonOnSerializedFullName) != null;
+                        implementsIJsonOnSerializing = interfaces.FirstOrDefault(interfaceName => interfaceName == JsonConstants.IJsonOnSerializingFullName) != null;
 
                         propGenSpecList = new List<PropertyGenerationSpec>();
                         Dictionary<string, PropertyGenerationSpec>? ignoredMembers = null;
index 399b893..51b548e 100644 (file)
@@ -18,10 +18,6 @@ namespace System.Text.Json.SourceGeneration
     [Generator]
     public sealed partial class JsonSourceGenerator : ISourceGenerator
     {
-        private const string SystemTextJsonSourceGenerationName = "System.Text.Json.SourceGeneration";
-        private const string IJsonOnSerializedFullName = "System.Text.Json.Serialization.IJsonOnSerialized";
-        private const string IJsonOnSerializingFullName = "System.Text.Json.Serialization.IJsonOnSerializing";
-
         /// <summary>
         /// Registers a syntax resolver to receive compilation units.
         /// </summary>
index 964f129..50d76bf 100644 (file)
@@ -7,6 +7,7 @@ using System.Collections.Immutable;
 using System.Globalization;
 using System.Linq;
 using System.Reflection;
+using System.Text.Json;
 using Microsoft.CodeAnalysis;
 
 namespace System.Text.Json.Reflection
@@ -139,7 +140,7 @@ namespace System.Text.Json.Reflection
                             sb.Insert(0, $"{currentSymbol.Name}+");
                         }
 
-                        if (!string.IsNullOrWhiteSpace(Namespace))
+                        if (!string.IsNullOrWhiteSpace(Namespace) && Namespace != JsonConstants.GlobalNamespaceValue)
                         {
                             sb.Insert(0, $"{Namespace}.");
                         }
index 0bdc4da..de7b135 100644 (file)
@@ -39,6 +39,7 @@
     <Compile Include="..\Common\ReflectionExtensions.cs" Link="Common\System\Text\Json\Serialization\ReflectionExtensions.cs" />
     <Compile Include="ClassType.cs" />
     <Compile Include="CollectionType.cs" />
+    <Compile Include="JsonConstants.cs" />
     <Compile Include="JsonSourceGenerator.cs" />
     <Compile Include="JsonSourceGenerator.Emitter.cs" />
     <Compile Include="JsonSourceGenerator.Parser.cs" />
index d654c01..f12e5b0 100644 (file)
@@ -389,6 +389,65 @@ namespace System.Text.Json.Serialization
             CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics());
         }
 
+        [Fact]
+        public void ContextTypeNotInNamespace()
+        {
+            string source = @"
+            using System.Text.Json.Serialization;
+
+            [JsonSerializable(typeof(MyType))]
+            internal partial class JsonContext : JsonSerializerContext
+            {
+            }
+
+            public class MyType
+            {
+                public int PublicPropertyInt { get; set; }
+                public string PublicPropertyString { get; set; }
+                private int PrivatePropertyInt { get; set; }
+                private string PrivatePropertyString { get; set; }
+
+                public double PublicDouble;
+                public char PublicChar;
+                private double PrivateDouble;
+                private char PrivateChar;
+
+                public void MyMethod() { }
+                public void MySecondMethod() { }
+
+                public void UsePrivates()
+                {
+                    PrivateDouble = 0;
+                    PrivateChar = ' ';
+                    double d = PrivateDouble;
+                    char c = PrivateChar;
+                }
+            }";
+
+            Compilation compilation = CompilationHelper.CreateCompilation(source);
+
+            JsonSourceGenerator generator = new JsonSourceGenerator();
+
+            Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray<Diagnostic> generatorDiags, generator);
+
+            // Make sure compilation was successful.
+            CheckCompilationDiagnosticsErrors(generatorDiags);
+            CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics());
+
+            Dictionary<string, Type> types = generator.GetSerializableTypes();
+
+            // Check base functionality of found types.
+            Assert.Equal(1, types.Count);
+            Type myType = types["MyType"];
+            Assert.Equal("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, expectedFieldNames, expectedPropertyNames, expectedMethodNames);
+        }
+
         private void CheckCompilationDiagnosticsErrors(ImmutableArray<Diagnostic> diagnostics)
         {
             Assert.Empty(diagnostics.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error));