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

Refine the client-namespace-conflict diagnostic #6161

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

ArcturusZhang
Copy link
Member

@ArcturusZhang ArcturusZhang commented Feb 26, 2025

Fixes Azure/autorest.csharp#5241

I removed the report diagnostic in our emitter - because not every generator would have this issue
and move the diagnostic reporting into our C# generator, when we build the namespace for a client type.

I also added a few test cases for the Emitter class in our C# generator, which leads to series of other changes about the disposing issue.

@microsoft-github-policy-service microsoft-github-policy-service bot added the emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp label Feb 26, 2025
@ArcturusZhang ArcturusZhang force-pushed the namespace-diagnostic-update branch from 694d17a to 07c774f Compare February 26, 2025 10:02
@azure-sdk
Copy link
Collaborator

API change check

API changes are not detected in this pull request.

private const string namespaceConflictCode = "client-namespace-conflict";

private string? _namespace;
protected override string BuildNamespace() => _namespace ??= BuildNamespaceCore();
Copy link
Member Author

Choose a reason for hiding this comment

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

this method is called multiple times - but we expect it should always return the same result, therefore I added the cache here.
If this is called multiple times, the diagnostic would be reported multiple times which is quite annoying

Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this called multiple times?

Copy link
Member Author

Choose a reason for hiding this comment

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

actually, twice:
first time here:


second time here:
=> CodeModelPlugin.Instance.SourceInputModel.FindForType(BuildNamespace(), BuildName());

we cannot change the second invocation to Namespace, because it would cause infinite invocation of this method.
Therefore I think we cannot actually resolve it


private Emitter()
internal Emitter()
Copy link
Member Author

Choose a reason for hiding this comment

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

changing to internal because with the singleton pattern, the emitter would be disposed when one test case is done, and the stream would be disposed and we cannot get things out of it then.

/// <param name="left">the first namespace</param>
/// <param name="right">the second namespace</param>
/// <returns></returns>
public static bool IsLastNamespaceSegmentTheSame(string left, string right)
Copy link
Member Author

@ArcturusZhang ArcturusZhang Feb 27, 2025

Choose a reason for hiding this comment

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

I introduce this method to verify if the namespace has been changed in one client
I added tests for this to verify it is working properly.

@ArcturusZhang ArcturusZhang marked this pull request as ready for review February 27, 2025 07:25
@@ -46,6 +47,7 @@ public CodeModelPlugin(GeneratorContext context)
Configuration = context.Configuration;
_inputLibrary = new InputLibrary(Configuration.OutputDirectory);
TypeFactory = new TypeFactory();
Emitter = null!;
Copy link
Member Author

Choose a reason for hiding this comment

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

we will inject a value for this property later after the plugin was loaded.

Copy link
Contributor

Choose a reason for hiding this comment

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

We should throw from the getter instead of doing this. Similar to what we do for other non-nullable properties.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure let me change

Copy link
Member Author

Choose a reason for hiding this comment

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

#6161 (comment)
I changed this so that the emitter could be initialized here.

}

public static Emitter Instance => _emitter ??= new Emitter();
Copy link
Member Author

@ArcturusZhang ArcturusZhang Feb 27, 2025

Choose a reason for hiding this comment

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

the singleton pattern was messing things up in our test cases because it might dispose the stream prematurely
therefore I decide to remove the singleton pattern here, and when we load the plugin, we inject the constructed instance in main into the CodeModelPlugin.
We have using statement in the Main method therefore this emitter instance would only be dispose when the program exits.

@@ -69,6 +70,8 @@ public static Mock<CodeModelPlugin> LoadMockPlugin(
// initialize the singleton instance of the plugin
var mockPlugin = new Mock<CodeModelPlugin>(new GeneratorContext(Configuration.Load(configFilePath, configuration))) { CallBase = true };

mockPlugin.Setup(p => p.Emitter).Returns(new Emitter(Console.OpenStandardOutput()));
Copy link
Member Author

Choose a reason for hiding this comment

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

I did not add a hook here so that we could mock it in our UT - but if in the future we need to check something, we could do it here.


namespace Microsoft.TypeSpec.Generator
{
internal class PluginHandler
{
public void LoadPlugin(CommandLineOptions options)
public void LoadPlugin(Emitter emitter, CommandLineOptions options)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need the emitter param?

Copy link
Member Author

Choose a reason for hiding this comment

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

See this comment and I have changed this back

@@ -30,6 +31,7 @@ internal void SelectPlugin(CommandLineOptions options)
{
CodeModelPlugin.Instance = plugin.Value;
CodeModelPlugin.Instance.IsNewProject = options.IsNewProject;
CodeModelPlugin.Instance.Emitter = emitter;
Copy link
Contributor

@JoshLove-msft JoshLove-msft Feb 27, 2025

Choose a reason for hiding this comment

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

Can we just construct the emitter here, and call Dispose on the instance from Program.cs?

Copy link
Member Author

@ArcturusZhang ArcturusZhang Feb 28, 2025

Choose a reason for hiding this comment

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

Previously Program needs this emitter to write debug information when we attach the debugger.
I think it should be fine if we just simply write the information into stderr - normal users would not come across it.
Now the emitter has been changed to initialize inside the CodeModelPlugin - and Program disposes it if it is still there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp
Projects
None yet
Development

Successfully merging this pull request may close these issues.

autorest.csharp should not report the @typespec/http-client-csharp/client-namespace-conflict diagnostic
3 participants