From 6ae4a962ee2c3d52e61326fdeff0420d335e592f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 1 Jun 2026 16:10:06 +0000
Subject: [PATCH 1/5] Initial plan
From 0f3541d1a0a03b6b50b16f6b3f9ef32d6a9ffe06 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 1 Jun 2026 16:20:17 +0000
Subject: [PATCH 2/5] Downgrade convenience method to internal when a parameter
type is internal
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
---
.../Providers/ScmMethodProviderCollection.cs | 25 ++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs
index f8bd0588eac..83b29533b36 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs
@@ -152,7 +152,7 @@ private ScmMethodProvider BuildConvenienceMethod(MethodProvider protocolMethod,
methodSignature = new MethodSignature(
methodName,
DocHelpers.GetFormattableDescription(ServiceMethod.Operation.Summary, ServiceMethod.Operation.Doc) ?? FormattableStringHelpers.FromString(ServiceMethod.Operation.Name),
- protocolMethod.Signature.Modifiers,
+ GetConvenienceMethodModifiers(protocolMethod.Signature.Modifiers, signatureParameters),
GetResponseType(ServiceMethod.Operation.Responses, true, isAsync, out _),
null,
signatureParameters);
@@ -215,6 +215,29 @@ .. GetStackVariablesForReturnValueConversion(result, responseBodyType, isAsync,
return convenienceMethod;
}
+ ///
+ /// Determines the modifiers for a convenience method. When a convenience method would be
+ /// generated as public on a public type but has a parameter whose type is internal (for example,
+ /// a model that was customized to be internal via client.tsp or custom code), the method is
+ /// downgraded to internal to avoid an inconsistent accessibility compilation error.
+ ///
+ private MethodSignatureModifiers GetConvenienceMethodModifiers(
+ MethodSignatureModifiers modifiers,
+ IReadOnlyList signatureParameters)
+ {
+ // Only public methods on a public enclosing type are affected. If the enclosing type is
+ // already internal, the convenience method is effectively internal and needs no adjustment.
+ if (modifiers.HasFlag(MethodSignatureModifiers.Public) &&
+ EnclosingType.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) &&
+ signatureParameters.Any(p => !p.Type.IsPublic))
+ {
+ modifiers &= ~MethodSignatureModifiers.Public;
+ modifiers |= MethodSignatureModifiers.Internal;
+ }
+
+ return modifiers;
+ }
+
private IEnumerable GetStackVariablesForProtocolParamConversion(IReadOnlyList convenienceMethodParameters, out Dictionary declarations)
{
List statements = new List();
From 235d7d7987415c1fe8a78002cf1c715fb6bcb383 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 1 Jun 2026 16:34:00 +0000
Subject: [PATCH 3/5] Add unit tests for internal-parameter convenience method
accessibility
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
---
.../ScmMethodProviderCollectionTests.cs | 91 +++++++++++++++++++
1 file changed, 91 insertions(+)
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs
index 65e6edf5982..6c1d2595118 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs
@@ -2001,5 +2001,96 @@ public void ConvenienceMethod_JsonListBody_DoesNotUseXmlFromEnumerable()
Assert.IsFalse(methodBody.Contains("rootNameHint"));
Assert.IsFalse(methodBody.Contains("childNameHint"));
}
+
+ [Test]
+ public void ConvenienceMethod_WithInternalParameterType_IsInternal()
+ {
+ // A model that is customized to be internal (for example via client.tsp) results in a
+ // convenience method parameter whose type is internal. The convenience method must be
+ // generated as internal to avoid an inconsistent accessibility compilation error.
+ var internalModel = InputFactory.Model(
+ "InternalModel",
+ access: "internal",
+ usage: InputModelTypeUsage.Input | InputModelTypeUsage.Json,
+ properties: [InputFactory.Property("Name", InputPrimitiveType.String)]);
+
+ var bodyParam = InputFactory.BodyParameter("body", internalModel, isRequired: true);
+ var methodBodyParam = InputFactory.MethodParameter(
+ "body",
+ internalModel,
+ isRequired: true,
+ location: InputRequestLocation.Body);
+
+ var operation = InputFactory.Operation(
+ "Foo",
+ httpMethod: "POST",
+ parameters: [bodyParam],
+ responses: [InputFactory.OperationResponse([200])]);
+
+ var serviceMethod = InputFactory.BasicServiceMethod("Foo", operation, parameters: [methodBodyParam]);
+ var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]);
+
+ MockHelpers.LoadMockGenerator(clients: () => [inputClient], inputModels: () => [internalModel]);
+
+ var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);
+ var methodCollection = new ScmMethodProviderCollection(serviceMethod, client!);
+
+ var convenienceMethods = methodCollection.Where(m =>
+ m.Signature.Parameters.All(p => p.Name != "content") &&
+ m.Signature.Name.StartsWith("Foo")).ToList();
+ Assert.AreEqual(2, convenienceMethods.Count);
+
+ foreach (var convenienceMethod in convenienceMethods)
+ {
+ // The parameter type is internal.
+ Assert.IsTrue(convenienceMethod.Signature.Parameters.Any(p => !p.Type.IsPublic));
+ // The convenience method should therefore be internal, not public.
+ Assert.IsTrue(convenienceMethod.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal));
+ Assert.IsFalse(convenienceMethod.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public));
+ }
+ }
+
+ [Test]
+ public void ConvenienceMethod_WithPublicParameterType_IsPublic()
+ {
+ // When all convenience method parameter types are public, the method should remain public.
+ var publicModel = InputFactory.Model(
+ "PublicModel",
+ usage: InputModelTypeUsage.Input | InputModelTypeUsage.Json,
+ properties: [InputFactory.Property("Name", InputPrimitiveType.String)]);
+
+ var bodyParam = InputFactory.BodyParameter("body", publicModel, isRequired: true);
+ var methodBodyParam = InputFactory.MethodParameter(
+ "body",
+ publicModel,
+ isRequired: true,
+ location: InputRequestLocation.Body);
+
+ var operation = InputFactory.Operation(
+ "Foo",
+ httpMethod: "POST",
+ parameters: [bodyParam],
+ responses: [InputFactory.OperationResponse([200])]);
+
+ var serviceMethod = InputFactory.BasicServiceMethod("Foo", operation, parameters: [methodBodyParam]);
+ var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]);
+
+ MockHelpers.LoadMockGenerator(clients: () => [inputClient], inputModels: () => [publicModel]);
+
+ var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);
+ var methodCollection = new ScmMethodProviderCollection(serviceMethod, client!);
+
+ var convenienceMethods = methodCollection.Where(m =>
+ m.Signature.Parameters.All(p => p.Name != "content") &&
+ m.Signature.Name.StartsWith("Foo")).ToList();
+ Assert.AreEqual(2, convenienceMethods.Count);
+
+ foreach (var convenienceMethod in convenienceMethods)
+ {
+ Assert.IsTrue(convenienceMethod.Signature.Parameters.All(p => p.Type.IsPublic));
+ Assert.IsTrue(convenienceMethod.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public));
+ Assert.IsFalse(convenienceMethod.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal));
+ }
+ }
}
}
From f0662a7a600c8b602e65ba77a0a7250697145c9b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 1 Jun 2026 16:50:01 +0000
Subject: [PATCH 4/5] Address review: remove xml docs, broaden enclosing type
check, add custom-code test
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
---
.../Providers/ScmMethodProviderCollection.cs | 12 ++---
.../ScmMethodProviderCollectionTests.cs | 52 ++++++++++++++++++-
.../CustomInternalModel.cs | 9 ++++
3 files changed, 63 insertions(+), 10 deletions(-)
create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethod_WithCustomCodeInternalParameterType_IsInternal/CustomInternalModel.cs
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs
index 83b29533b36..1441718d0a5 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs
@@ -215,20 +215,14 @@ .. GetStackVariablesForReturnValueConversion(result, responseBodyType, isAsync,
return convenienceMethod;
}
- ///
- /// Determines the modifiers for a convenience method. When a convenience method would be
- /// generated as public on a public type but has a parameter whose type is internal (for example,
- /// a model that was customized to be internal via client.tsp or custom code), the method is
- /// downgraded to internal to avoid an inconsistent accessibility compilation error.
- ///
private MethodSignatureModifiers GetConvenienceMethodModifiers(
MethodSignatureModifiers modifiers,
IReadOnlyList signatureParameters)
{
- // Only public methods on a public enclosing type are affected. If the enclosing type is
- // already internal, the convenience method is effectively internal and needs no adjustment.
+ var enclosingTypeModifiers = EnclosingType.DeclarationModifiers;
if (modifiers.HasFlag(MethodSignatureModifiers.Public) &&
- EnclosingType.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) &&
+ !enclosingTypeModifiers.HasFlag(TypeSignatureModifiers.Internal) &&
+ !enclosingTypeModifiers.HasFlag(TypeSignatureModifiers.Private) &&
signatureParameters.Any(p => !p.Type.IsPublic))
{
modifiers &= ~MethodSignatureModifiers.Public;
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs
index 6c1d2595118..d54a65c643c 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs
@@ -2005,7 +2005,7 @@ public void ConvenienceMethod_JsonListBody_DoesNotUseXmlFromEnumerable()
[Test]
public void ConvenienceMethod_WithInternalParameterType_IsInternal()
{
- // A model that is customized to be internal (for example via client.tsp) results in a
+ // A model that is customized to be internal via client.tsp (an access override) results in a
// convenience method parameter whose type is internal. The convenience method must be
// generated as internal to avoid an inconsistent accessibility compilation error.
var internalModel = InputFactory.Model(
@@ -2050,6 +2050,56 @@ public void ConvenienceMethod_WithInternalParameterType_IsInternal()
}
}
+ [Test]
+ public async Task ConvenienceMethod_WithCustomCodeInternalParameterType_IsInternal()
+ {
+ // A model that is customized to be internal via custom code (an internal partial class)
+ // results in a convenience method parameter whose type is internal. The convenience method
+ // must be generated as internal to avoid an inconsistent accessibility compilation error.
+ var customInternalModel = InputFactory.Model(
+ "CustomInternalModel",
+ usage: InputModelTypeUsage.Input | InputModelTypeUsage.Json,
+ properties: [InputFactory.Property("Name", InputPrimitiveType.String)]);
+
+ var bodyParam = InputFactory.BodyParameter("body", customInternalModel, isRequired: true);
+ var methodBodyParam = InputFactory.MethodParameter(
+ "body",
+ customInternalModel,
+ isRequired: true,
+ location: InputRequestLocation.Body);
+
+ var operation = InputFactory.Operation(
+ "Foo",
+ httpMethod: "POST",
+ parameters: [bodyParam],
+ responses: [InputFactory.OperationResponse([200])]);
+
+ var serviceMethod = InputFactory.BasicServiceMethod("Foo", operation, parameters: [methodBodyParam]);
+ var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]);
+
+ await MockHelpers.LoadMockGeneratorAsync(
+ compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(),
+ clients: () => [inputClient],
+ inputModels: () => [customInternalModel]);
+
+ var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);
+ var methodCollection = new ScmMethodProviderCollection(serviceMethod, client!);
+
+ var convenienceMethods = methodCollection.Where(m =>
+ m.Signature.Parameters.All(p => p.Name != "content") &&
+ m.Signature.Name.StartsWith("Foo")).ToList();
+ Assert.AreEqual(2, convenienceMethods.Count);
+
+ foreach (var convenienceMethod in convenienceMethods)
+ {
+ // The parameter type is internal because it was customized via custom code.
+ Assert.IsTrue(convenienceMethod.Signature.Parameters.Any(p => !p.Type.IsPublic));
+ // The convenience method should therefore be internal, not public.
+ Assert.IsTrue(convenienceMethod.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal));
+ Assert.IsFalse(convenienceMethod.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public));
+ }
+ }
+
[Test]
public void ConvenienceMethod_WithPublicParameterType_IsPublic()
{
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethod_WithCustomCodeInternalParameterType_IsInternal/CustomInternalModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethod_WithCustomCodeInternalParameterType_IsInternal/CustomInternalModel.cs
new file mode 100644
index 00000000000..72f19011ba7
--- /dev/null
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethod_WithCustomCodeInternalParameterType_IsInternal/CustomInternalModel.cs
@@ -0,0 +1,9 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+namespace Sample.Models
+{
+ internal partial class CustomInternalModel
+ {
+ }
+}
From e6269da63b61cfe2b7fbb4ebd74dc04d7dc32837 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 1 Jun 2026 17:00:14 +0000
Subject: [PATCH 5/5] Validate convenience method tests against client.Methods
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
---
.../test/Providers/ScmMethodProviderCollectionTests.cs | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs
index d54a65c643c..6bd6a7743c5 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs
@@ -2033,9 +2033,8 @@ public void ConvenienceMethod_WithInternalParameterType_IsInternal()
MockHelpers.LoadMockGenerator(clients: () => [inputClient], inputModels: () => [internalModel]);
var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);
- var methodCollection = new ScmMethodProviderCollection(serviceMethod, client!);
- var convenienceMethods = methodCollection.Where(m =>
+ var convenienceMethods = client!.Methods.Where(m =>
m.Signature.Parameters.All(p => p.Name != "content") &&
m.Signature.Name.StartsWith("Foo")).ToList();
Assert.AreEqual(2, convenienceMethods.Count);
@@ -2083,9 +2082,8 @@ await MockHelpers.LoadMockGeneratorAsync(
inputModels: () => [customInternalModel]);
var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);
- var methodCollection = new ScmMethodProviderCollection(serviceMethod, client!);
- var convenienceMethods = methodCollection.Where(m =>
+ var convenienceMethods = client!.Methods.Where(m =>
m.Signature.Parameters.All(p => p.Name != "content") &&
m.Signature.Name.StartsWith("Foo")).ToList();
Assert.AreEqual(2, convenienceMethods.Count);
@@ -2128,9 +2126,8 @@ public void ConvenienceMethod_WithPublicParameterType_IsPublic()
MockHelpers.LoadMockGenerator(clients: () => [inputClient], inputModels: () => [publicModel]);
var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);
- var methodCollection = new ScmMethodProviderCollection(serviceMethod, client!);
- var convenienceMethods = methodCollection.Where(m =>
+ var convenienceMethods = client!.Methods.Where(m =>
m.Signature.Parameters.All(p => p.Name != "content") &&
m.Signature.Name.StartsWith("Foo")).ToList();
Assert.AreEqual(2, convenienceMethods.Count);