-
Notifications
You must be signed in to change notification settings - Fork 339
fix: generate parameterized accessor for subclients with subclient-specific parameters #9919
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
Open
Copilot
wants to merge
12
commits into
main
Choose a base branch
from
copilot/add-subclient-parameters-accessors
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+319
−55
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
1389cb6
Initial plan
Copilot 624a489
fix(http-client-js): include subclient parameters in accessor methods
Copilot 533c12b
fix(http-client-csharp): include subclient parameters in accessor met…
Copilot 8c982c7
revert: restore http-client-js/CHANGELOG.md to original state
Copilot 08eb7b2
chore: delete stale http-client-csharp changeset file
Copilot afdc1da
fix: parameterized accessor for IndividuallyParent subclients with ac…
Copilot d716f43
revert: remove Spector azure test project per review feedback
Copilot dd06669
style: run prettier on SampleService/main.tsp
Copilot cb0f232
chore: regenerate Sample-TypeSpec after Generate.ps1
Copilot 5166912
Changes before error encountered
Copilot 8d161f2
refactor: simplify subclient accessor params - remove propagation det…
Copilot 4cbd977
fix: add MetricsClientParams back to main.tsp and regenerate samples
JoshLove-msft File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -309,6 +309,24 @@ private IReadOnlyList<ParameterProvider> GetSubClientInternalConstructorParamete | |
| return subClientParameters; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines whether this subclient has non-infrastructure parameters that need to be | ||
| /// included as parameters in the parent's accessor method rather than stored on the parent. | ||
| /// A subclient has accessor-only parameters if it has non-infrastructure parameters | ||
| /// (not API versions, not endpoint) that are not present on the parent's InputClient.Parameters. | ||
| /// Uses the raw <see cref="InputClient.Parameters"/> to avoid circular lazy-initialization dependencies. | ||
| /// </summary> | ||
| internal bool HasAccessorOnlyParameters(InputClient parentInputClient) | ||
JoshLove-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| var parentParamNames = parentInputClient.Parameters | ||
| .Select(p => p.Name) | ||
| .ToHashSet(StringComparer.OrdinalIgnoreCase); | ||
|
|
||
| return _inputClient.Parameters | ||
| .Where(p => !p.IsApiVersion && !(p is InputEndpointParameter ep && ep.IsEndpoint)) | ||
| .Any(p => !parentParamNames.Contains(p.Name)); | ||
| } | ||
|
|
||
| private Lazy<IReadOnlyList<ParameterProvider>> _clientParameters; | ||
| internal IReadOnlyList<ParameterProvider> ClientParameters => _clientParameters.Value; | ||
| private IReadOnlyList<ParameterProvider> GetClientParameters() | ||
|
|
@@ -397,7 +415,10 @@ protected override FieldProvider[] BuildFields() | |
| // add sub-client caching fields | ||
| foreach (var subClient in _subClients.Value) | ||
| { | ||
| if (subClient._clientCachingField != null) | ||
| // Only add caching field when the accessor does not require additional parameters. | ||
| // If the subclient has parameters that are not on the parent, each accessor call may | ||
| // produce a different client instance, so caching is not appropriate. | ||
| if (subClient._clientCachingField != null && !subClient.HasAccessorOnlyParameters(_inputClient)) | ||
| { | ||
| fields.Add(subClient._clientCachingField); | ||
| } | ||
|
|
@@ -899,8 +920,20 @@ protected override ScmMethodProvider[] BuildMethods() | |
|
|
||
| var cachedClientFieldVar = new VariableExpression(subClient.Type, subClient._clientCachingField.Declaration, IsRef: true); | ||
| List<ValueExpression> subClientConstructorArgs = new(3); | ||
|
|
||
| // Populate constructor arguments | ||
| List<ParameterProvider> accessorMethodParams = []; | ||
|
|
||
| // Determine which subclient parameters should be on the accessor method. | ||
| // Subclient-specific parameters (not on the parent) need to be passed via the accessor. | ||
| var parentEffectiveParamNames = _allClientParameters | ||
| .Select(p => p.Name) | ||
| .ToHashSet(StringComparer.OrdinalIgnoreCase); | ||
| var subClientExtraInputParamNames = subClient._inputClient.Parameters | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we actually need this logic to compare subclient and parent parameters? I think any parameters on the subclient are subclient-specific. Is that not the case? |
||
| .Where(p => !p.IsApiVersion && !(p is InputEndpointParameter ep && ep.IsEndpoint)) | ||
| .Where(p => !parentEffectiveParamNames.Contains(p.Name)) | ||
| .Select(p => p.Name) | ||
| .ToHashSet(StringComparer.OrdinalIgnoreCase); | ||
|
|
||
| // Populate constructor arguments, collecting extra params for the accessor method signature | ||
| foreach (var param in subClient._subClientInternalConstructorParams.Value) | ||
| { | ||
| if (parentClientProperties.TryGetValue(param.Name, out var parentProperty)) | ||
|
|
@@ -920,30 +953,59 @@ protected override ScmMethodProvider[] BuildMethods() | |
| subClientConstructorArgs.Add(correspondingApiVersionField.Field); | ||
| } | ||
| } | ||
| else if (subClientExtraInputParamNames.Contains(param.Name)) | ||
| { | ||
| // This parameter is subclient-specific and not available on the parent -- | ||
| // expose it as an accessor method parameter. | ||
| accessorMethodParams.Add(param); | ||
| subClientConstructorArgs.Add(param); | ||
| } | ||
| // else: infra param (pipeline, auth, endpoint) not found in parent mock — silently skip | ||
| } | ||
|
|
||
| // Create the interlocked compare exchange expression for the body | ||
| var interlockedCompareExchange = Static(typeof(Interlocked)).Invoke( | ||
| nameof(Interlocked.CompareExchange), | ||
| [cachedClientFieldVar, New.Instance(subClient.Type, subClientConstructorArgs), Null]); | ||
| var factoryMethodName = subClient.Name.EndsWith(ClientSuffix, StringComparison.OrdinalIgnoreCase) | ||
| ? $"Get{subClient.Name}" | ||
| : $"Get{subClient.Name}{ClientSuffix}"; | ||
|
|
||
| var factoryMethod = new ScmMethodProvider( | ||
| new( | ||
| factoryMethodName, | ||
| $"Initializes a new instance of {subClient.Type.Name}", | ||
| MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, | ||
| subClient.Type, | ||
| null, | ||
| []), | ||
| // return Volatile.Read(ref _cachedClient) ?? Interlocked.CompareExchange(ref _cachedClient, new Client(_pipeline, _keyCredential, _endpoint), null) ?? _cachedClient; | ||
| Return( | ||
| Static(typeof(Volatile)).Invoke(nameof(Volatile.Read), cachedClientFieldVar) | ||
| .NullCoalesce(interlockedCompareExchange.NullCoalesce(subClient._clientCachingField))), | ||
| this, | ||
| ScmMethodKind.Convenience); | ||
| ScmMethodProvider factoryMethod; | ||
| if (accessorMethodParams.Count > 0) | ||
| { | ||
| // When the accessor requires extra parameters, caching is not appropriate | ||
| // (different parameter values may produce different client instances). | ||
| // Return a new instance directly. | ||
| factoryMethod = new ScmMethodProvider( | ||
| new( | ||
| factoryMethodName, | ||
| $"Initializes a new instance of {subClient.Type.Name}", | ||
| MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, | ||
| subClient.Type, | ||
| null, | ||
| [.. accessorMethodParams]), | ||
| Return(New.Instance(subClient.Type, subClientConstructorArgs)), | ||
| this, | ||
| ScmMethodKind.Convenience); | ||
| } | ||
| else | ||
| { | ||
| // No extra params - use the existing caching pattern | ||
| var interlockedCompareExchange = Static(typeof(Interlocked)).Invoke( | ||
| nameof(Interlocked.CompareExchange), | ||
| [cachedClientFieldVar, New.Instance(subClient.Type, subClientConstructorArgs), Null]); | ||
| factoryMethod = new ScmMethodProvider( | ||
| new( | ||
| factoryMethodName, | ||
| $"Initializes a new instance of {subClient.Type.Name}", | ||
| MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, | ||
| subClient.Type, | ||
| null, | ||
| []), | ||
| // return Volatile.Read(ref _cachedClient) ?? Interlocked.CompareExchange(ref _cachedClient, new Client(_pipeline, _keyCredential, _endpoint), null) ?? _cachedClient; | ||
| Return( | ||
| Static(typeof(Volatile)).Invoke(nameof(Volatile.Read), cachedClientFieldVar) | ||
| .NullCoalesce(interlockedCompareExchange.NullCoalesce(subClient._clientCachingField))), | ||
| this, | ||
| ScmMethodKind.Convenience); | ||
| } | ||
| methods.Add(factoryMethod); | ||
| } | ||
|
|
||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.