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

Options validation source generator #87587

Merged
merged 13 commits into from Jun 20, 2023
Merged

Conversation

tarekgh
Copy link
Member

@tarekgh tarekgh commented Jun 15, 2023

No description provided.

@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@ghost
Copy link

ghost commented Jun 15, 2023

Tagging subscribers to this area: @dotnet/area-extensions-options
See info in area-owners.md if you want to be subscribed.

Issue Details

null

Author: tarekgh
Assignees: tarekgh
Labels:

new-api-needs-documentation, area-Extensions-Options

Milestone: -

@tarekgh tarekgh added this to the 8.0.0 milestone Jun 15, 2023
Copy link
Member

@jeffhandley jeffhandley left a comment

Choose a reason for hiding this comment

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

I was just skimming to get an overview, and I saw a couple cosmetics I left comments on.

HashSet<string> fullyQualifiedAttributeNames,
Action<Compilation, IEnumerable<SyntaxNode>, SourceProductionContext> process) => Initialize(context, fullyQualifiedAttributeNames, x => x, process);

public static void Initialize(
Copy link
Member

Choose a reason for hiding this comment

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

I would strongly recommend not taking a dependency on this pattern, since it makes it impractical for the source generator to take advantage of incremental caching. The general pattern is to extract data as early as possible from the compilation before applying it to a SourceProductionContext. You can find more details in the Roslyn guide as well as see real-world examples in runtime here:

  1. JsonSourceGenerator
  2. RegexGenerator
  3. LibraryImportGenerator

cc @chsienki

@tarekgh
Copy link
Member Author

tarekgh commented Jun 16, 2023

@eiriktsarpalis could you please have a look at the last commit? Finally, I got it work using ForAttributeWithMetadataName.

@tarekgh
Copy link
Member Author

tarekgh commented Jun 16, 2023

CC @geeknoid

string Name,
string SimpleName,
bool SelfValidates,
List<ValidatedMember> MembersToValidate);
Copy link
Member

Choose a reason for hiding this comment

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

Instead of List, consider using a collection type with sequence equality so that your model has the desired equality semantics. Here's one from the repo that you can use:

https://github.com/dotnet/runtime/blob/be6a1389c852fdc96a919769867fb3f3ddb0fbde/src/libraries/System.Text.Json/gen/Helpers/ImmutableEquatableArray.cs

You could try moving the class to the common folder so that it's shared between the two projects.

string Name,
string NameWithoutGenerics,
string DeclarationKeyword,
List<string> ParentTypes,
Copy link
Member

Choose a reason for hiding this comment

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


var parser = new Parser(compilation, context.ReportDiagnostic, symbolHolder!, context.CancellationToken);

var validatorTypes = parser.GetValidatorTypes(types);
Copy link
Member

Choose a reason for hiding this comment

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

As a next step, you might want to move the parser logic inside an IncrementalValuesProvider.Select declaration so that you end up with an IncrementalValuesProvider<ValidatorType>.

To achieve this, the Parser class needs to be updated so that it just stores Diagnostic values, to be reported by the SourceProductionContext later:

public List<DiagnosticInfo> Diagnostics { get; } = new();
public void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location, params object?[]? messageArgs)
{
Diagnostics.Add(new DiagnosticInfo
{
Descriptor = descriptor,
Location = location.GetTrimmedLocation(),
MessageArgs = messageArgs ?? Array.Empty<object?>(),
});
}

@eiriktsarpalis
Copy link
Member

Consider adding a separate unit test project on top of the existing source generator tests project which lets you test the source generator in isolation like so:

[Fact]
public void EquivalentTupleDeclarations_DoNotConflict()
{
string source = """
using System.Text.Json.Serialization;
#nullable enable
namespace HelloWorld
{
[JsonSerializable(typeof((string? Label1, string Label2, int Integer)))]
[JsonSerializable(typeof((string, string, int)))]
internal partial class JsonContext : JsonSerializerContext
{
}
}
""";
Compilation compilation = CompilationHelper.CreateCompilation(source);
JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation);
// Make sure compilation was successful.
Assert.Empty(result.Diagnostics);
Assert.Empty(result.NewCompilation.GetDiagnostics());
// Should find the generated type.
Assert.Equal(3, result.AllGeneratedTypes.Count());
result.AssertContainsType("(string, string, int)");
result.AssertContainsType("string");
result.AssertContainsType("int");
}

Among other things this lets you test for diagnostic warnings and incremental compilation properties.

…idator.TryValidateValue

Instead of validating each attribute separately, instead we collect the list of the attributes we need to validate and then call Validator.TryValidateValue. This will guarantee performing the same validation order as the runtime.
@tarekgh
Copy link
Member Author

tarekgh commented Jun 18, 2023

@eiriktsarpalis

Consider adding a separate unit test project on top of the existing source generator tests project which lets you test the source generator in isolation

I added that in the commit 54a2f97 (#87587). We can add more stuff there as needed.

Copy link
Member

@eiriktsarpalis eiriktsarpalis left a comment

Choose a reason for hiding this comment

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

Thanks

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.

None yet

6 participants