Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue diagnostic warnings when context types don't indicate serializable types #58768

Closed
wants to merge 1 commit into from

Conversation

layomia
Copy link
Contributor

@layomia layomia commented Sep 7, 2021

Fixes #58698. I propose porting to 6.0 for improved user experience for the src-gen feature.

With this PR we'll start issuing a diagnostic warning when a context type doesn't indicate serializable types. This warning is only logged when the context type contains no members. We assume that the user wants source generation to provide an implementation, e.g.

internal partial class MyJsonSerializerContext : JsonSerializerContext
{
}

In other cases (i.e. if the context type has at least one member), the user might have a handwritten context for which they don't need source generation. In those cases we don't issue a warning, e.g this example from our tests:

internal partial class JsonContext : JsonSerializerContext
{
    public override JsonTypeInfo? GetTypeInfo(Type type)
    {
        if (type == typeof(JsonMessage))
        {
            return JsonMessage;
        }

        return null;
    }

    public JsonTypeInfo<JsonMessage> JsonMessage => ...;
}   

@layomia layomia added this to the 6.0.0 milestone Sep 7, 2021
@layomia layomia self-assigned this Sep 7, 2021
@ghost
Copy link

ghost commented Sep 7, 2021

Tagging subscribers to this area: @eiriktsarpalis, @layomia
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #58698. I propose porting to 6.0 for improved user experience for the src-gen feature.

With this PR we'll start issuing a diagnostic warning when a context type doesn't indicate serializable types. This warning is only logged when the context type contains no members. We assume that the user wants source generation to provide an implementation, e.g.

internal partial class MyJsonSerializerContext : JsonSerializerContext
{
}

In other cases (i.e. if the context type has at least one member), the user might have a handwritten context for which they don't need source generation. In those cases we don't issue a warning, e.g this example from our tests:

internal partial class JsonContext : JsonSerializerContext
{
    public override JsonTypeInfo? GetTypeInfo(Type type)
    {
        if (type == typeof(JsonMessage))
        {
            return JsonMessage;
        }

        return null;
    }

    public JsonTypeInfo<JsonMessage> JsonMessage => ...;
}   
Author: layomia
Assignees: layomia
Labels:

area-System.Text.Json

Milestone: 6.0.0

@@ -407,31 +423,25 @@ private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [Not
return typeGenerationSpec;
}

internal static bool IsSyntaxTargetForGeneration(SyntaxNode node) => node is ClassDeclarationSyntax { AttributeLists: { Count: > 0 }, BaseList: { Types : {Count : > 0 } } };
internal static bool IsSyntaxTargetForGeneration(SyntaxNode node) => node is ClassDeclarationSyntax { BaseList: { Types : { Count : > 0 } } };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For diffs between here and EOF, I'm relaxing the criteria for types we consider for generation

Before: include all classes that are annotated with a type called "JsonSerializableAttribute", and also derive or implement at least one type.
After: include all classes that derive from a type called "JsonSerializerContext".

I believe this change is fine since we'll have about the same number of types to consider in most source gen scenarios.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this could impact perf. I would test the before/after of this change and ensure for large solutions that target net6.0 you aren't negatively impacting the perf.

I can get you a large solution offline, if you need it to test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible this change could also improve perf, depending on the individual performance attributes vs. base classes, and of course the actual solution and its types (do they have base classes, attributes, etc).

@eerhardt how do you suggest measuring perf? Manual measuring the VS experience?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericstj made a micro benchmark for source generators: https://github.com/ericstj/sample-code/tree/generatorBenchmark

But honestly, I would measure it in VS with a large solution. The way I measured the time taken was to add an EventSource around the source generator implementation. And then use PerfView to see how much time was spent in the source generator when it wasn't doing any work.

But if you are using the latest Roslyn builds (as of 5 days ago), this timing measurement is built in. See dotnet/roslyn#55892.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One possible way to mitigate would be check the member count of the type. In pseudocode:

internal static bool IsSyntaxTargetForGeneration(SyntaxNode node) =>
    node is ClassDeclarationSyntax { 
            { BaseList: { Types : { Count : > 0 } } and 
            { AttributeLists: { Count: > 0 } } or 
            { MemberList: { Count: 0 } } };

Note that this would contradict my feedback in https://github.com/dotnet/runtime/pull/58768/files#r704458155.

@@ -130,7 +130,7 @@
<value>Did not generate serialization metadata for type.</value>
</data>
<data name="ContextClassesMustBePartialMessageFormat" xml:space="preserve">
<value>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</value>
<value>Derived 'JsonSerializerContext' type '{0}' and all containing types must be made partial to kick off source generation.</value>
Copy link
Member

@steveharter steveharter Sep 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest rewording to avoid "kick off source generation":
"..and all containing types must be made partial for source generation."

(here and elsewhere)

if (serializableAttributeList == null)
{
// No types were indicated with [JsonSerializable]

// If there are no members on the context, assume that the user wanted source generation and notify them that there are no serializable types.
if (!contextTypeSymbol.MemberNames.Any())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My feeling is that we should refine this to check for the existence of GetTypeInfo overrides. A user that unintentionally adds any member to their context class (be it a helper method, type, etc.) would see this diagnostic disappear completely.

@@ -3,15 +3,25 @@
<file datatype="xml" source-language="en" target-language="cs" original="../Strings.resx">
<body>
<trans-unit id="ContextClassesMustBePartialMessageFormat">
<source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
<target state="translated">Odvozený typ JsonSerializerContext {0} určuje typy serializovatelné na JSON. Typ a všechny obsahující typy musí být částečné, aby se zahájilo generování zdroje.</target>
<source>Derived 'JsonSerializerContext' type '{0}' and all containing types must be made partial to kick off source generation.</source>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do backport this to release/6.0 now, isn't there a risk of translations going out of sync?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a 1 week SLA for translations in main. We have enough time to backport to 6.0. That backport will need to be tracked with an issue so we don't forget.

@eiriktsarpalis
Copy link
Member

This is a good quality of life improvement, although I'm not necessarily convinced it meets the bar for a 6.0 backport.

@layomia
Copy link
Contributor Author

layomia commented Sep 8, 2021

although I'm not necessarily convinced it meets the bar for a 6.0 backport.

I can see that, but would we consider it a breaking change to go from no diagnostic to issuing one in a future release?

@ericstj
Copy link
Member

ericstj commented Sep 8, 2021

It fails with an error today, right? I don't think adding a diagnostic to something that already fails to compile is breaking. Even cases where we add diagnostics to passing code is acceptable in a new release (this is what the roslyn-analyzers repo does with every new analyzer).

Is the case this diagnostic fires always an error case? In that sense I'm not sure anyone would even see the diagnostic as users tend to prioritize errors over warnings. Or are we filling in the abstracts with empty implementations?

@layomia
Copy link
Contributor Author

layomia commented Sep 10, 2021

@ericstj, yes this is already a failure case (due to CS0534) so adding a diagnostic in the future wouldn't be breaking.

Is the case this diagnostic fires always an error case?

It's not always an error case (depending on user intent), and we don't touch the user context unless they indicate serializable types and derive from JsonSerializerContext.

I'll close this PR for 6.0 and we can address in 7.

@layomia layomia closed this Sep 10, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Oct 10, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Issue diagnostic warnings when custom JsonSerializerContext types don't indicate serializable types
5 participants