From 56490f46ce9d2752bc186701afb24afd34956543 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:59:20 +0000 Subject: [PATCH 1/7] Initial plan From f31ee1416ed9698972417e1d858e800ffe5fb0d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:09:32 +0000 Subject: [PATCH 2/7] fix: CodeWriter field dedup now updates references via MemberExpression When CodeWriter deduplicates field identifiers (e.g., appending "0" to avoid name collisions), MemberExpression now uses the CodeWriterDeclaration to resolve the actual name instead of the original string. This ensures that references through FieldProvider.AsValueExpression use the deduped name consistently with the declaration. Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/12da064c-553f-40a0-a78f-c68d46f80fba --- .../src/Expressions/MemberExpression.cs | 16 +++++++++++++--- .../src/Providers/FieldProvider.cs | 6 +++++- .../test/Writers/CodeWriterDeclarationTests.cs | 16 ++++++++++++++++ ...dDedupUpdatesReferencesViaMemberExpression.cs | 3 +++ 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldDedupUpdatesReferencesViaMemberExpression.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs index d6a9be31011..d5e84c65c03 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Statements; @@ -10,6 +11,7 @@ public sealed record MemberExpression(ValueExpression? Inner, string MemberName) { public ValueExpression? Inner { get; internal set; } = Inner; public string MemberName { get; private set; } = MemberName; + internal CodeWriterDeclaration? Declaration { get; set; } internal override void Write(CodeWriter writer) { if (Inner is not null) @@ -17,9 +19,17 @@ internal override void Write(CodeWriter writer) Inner.Write(writer); writer.AppendRaw("."); } - // workaround to avoid Roslyn reducing properties named Object to object - // Should come up with a better approach - https://github.com/microsoft/typespec/issues/4724 - writer.AppendRaw(MemberName == "Object" && Inner == null ? $"this.{MemberName}" : MemberName); + + if (Declaration is not null) + { + writer.Append(Declaration); + } + else + { + // workaround to avoid Roslyn reducing properties named Object to object + // Should come up with a better approach - https://github.com/microsoft/typespec/issues/4724 + writer.AppendRaw(MemberName == "Object" && Inner == null ? $"this.{MemberName}" : MemberName); + } } internal override ValueExpression? Accept(LibraryVisitor visitor, MethodProvider method) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FieldProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FieldProvider.cs index c76b632ef46..cb411d1d53f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FieldProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FieldProvider.cs @@ -101,6 +101,10 @@ public void Update( _variable?.Update(name: name); _asMember?.Update(memberName: name); _declaration = null; + if (_asMember != null) + { + _asMember.Declaration = Declaration; + } InitializeParameter(); } @@ -148,6 +152,6 @@ private void InitializeParameter() } private MemberExpression? _asMember; - public static implicit operator MemberExpression(FieldProvider field) => field._asMember ??= new MemberExpression(null, field.Name); + public static implicit operator MemberExpression(FieldProvider field) => field._asMember ??= new MemberExpression(null, field.Name) { Declaration = field.Declaration }; } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs index 259c75ea069..3e9688db27d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs @@ -168,6 +168,22 @@ public void ScopeDeclaredTwiceForMethodSignatureParam() Assert.AreEqual(Helpers.GetExpectedFromFile(), codeWriter.ToString(false)); } + [Test] + public void FieldDedupUpdatesReferencesViaMemberExpression() + { + var type = new TestTypeProvider(); + var field1 = new FieldProvider(FieldModifiers.Private | FieldModifiers.ReadOnly, typeof(string), "_scope", type); + var field2 = new FieldProvider(FieldModifiers.Private | FieldModifiers.ReadOnly, typeof(string), "_scope", type); + + using var codeWriter = new CodeWriter(); + // Write both field declarations - the second should be deduped to _scope0 + codeWriter.WriteField(field1); + codeWriter.WriteField(field2); + // Write a reference to the second field via AsValueExpression (implicit MemberExpression) + field2.AsValueExpression.Write(codeWriter); + Assert.AreEqual(Helpers.GetExpectedFromFile(), codeWriter.ToString(false)); + } + private Dictionary GetDeclarationScopes(CodeWriterDeclaration declaration) { var namesDictionaryField = typeof(CodeWriterDeclaration).GetField("_actualNames", BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldDedupUpdatesReferencesViaMemberExpression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldDedupUpdatesReferencesViaMemberExpression.cs new file mode 100644 index 00000000000..93797a79ad5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldDedupUpdatesReferencesViaMemberExpression.cs @@ -0,0 +1,3 @@ +private readonly string _scope; +private readonly string _scope0; +_scope0 \ No newline at end of file From 250433652ee01c9806f580d4d2bbfde8db1d16df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:43:23 +0000 Subject: [PATCH 3/7] fix: use AppendDeclarationReference to avoid accidental scope declarations When MemberExpression.Write() used writer.Append(Declaration), the CodeWriter.Append method would call WriteDeclaration (registering a new deduped name) if the field hadn't been declared in the current scope yet. This caused field references like _token to become _token0. Added CodeWriter.AppendDeclarationReference() which resolves the deduped name when the declaration exists in scope, but falls back to the requested name otherwise (never creates new declarations). Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/45a95dc7-febe-41a8-b56b-b0aad13b473f --- .../src/Expressions/MemberExpression.cs | 2 +- .../src/Writers/CodeWriter.cs | 12 ++++++++++++ .../test/Writers/CodeWriterDeclarationTests.cs | 13 +++++++++++++ ...ldReferenceBeforeDeclarationUsesRequestedName.cs | 1 + 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldReferenceBeforeDeclarationUsesRequestedName.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs index d5e84c65c03..4ee235a72d3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs @@ -22,7 +22,7 @@ internal override void Write(CodeWriter writer) if (Declaration is not null) { - writer.Append(Declaration); + writer.AppendDeclarationReference(Declaration); } else { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs index f57b5435a4d..b44b8d605cc 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs @@ -1016,6 +1016,18 @@ internal void Append(CodeWriterDeclaration declaration) } } + internal void AppendDeclarationReference(CodeWriterDeclaration declaration) + { + if (declaration.HasBeenDeclared(_scopes)) + { + WriteIdentifier(declaration.GetActualName(_scopes.Peek())); + } + else + { + WriteIdentifier(declaration.RequestedName); + } + } + internal void WriteTypeModifiers(TypeSignatureModifiers modifiers) { AppendRawIf("public ", modifiers.HasFlag(TypeSignatureModifiers.Public)) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs index 3e9688db27d..907ffcebe43 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs @@ -184,6 +184,19 @@ public void FieldDedupUpdatesReferencesViaMemberExpression() Assert.AreEqual(Helpers.GetExpectedFromFile(), codeWriter.ToString(false)); } + [Test] + public void FieldReferenceBeforeDeclarationUsesRequestedName() + { + var type = new TestTypeProvider(); + var field = new FieldProvider(FieldModifiers.Private | FieldModifiers.ReadOnly, typeof(string), "_token", type); + + using var codeWriter = new CodeWriter(); + // Write a reference BEFORE the field declaration — should use the original name, not create a declaration + field.AsValueExpression.Write(codeWriter); + codeWriter.WriteField(field); + Assert.AreEqual(Helpers.GetExpectedFromFile(), codeWriter.ToString(false)); + } + private Dictionary GetDeclarationScopes(CodeWriterDeclaration declaration) { var namesDictionaryField = typeof(CodeWriterDeclaration).GetField("_actualNames", BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldReferenceBeforeDeclarationUsesRequestedName.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldReferenceBeforeDeclarationUsesRequestedName.cs new file mode 100644 index 00000000000..69338c17a95 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldReferenceBeforeDeclarationUsesRequestedName.cs @@ -0,0 +1 @@ +_tokenprivate readonly string _token; From 99f06054951746bebe28c3017300fb0897774a9a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:14:39 +0000 Subject: [PATCH 4/7] refactor: merge AppendDeclarationReference into Append with optional bool parameter Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/28b71dd2-6a3e-4a3b-89eb-3aebd1218fee --- .../src/Expressions/MemberExpression.cs | 2 +- .../src/Writers/CodeWriter.cs | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs index 4ee235a72d3..f4487534414 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs @@ -22,7 +22,7 @@ internal override void Write(CodeWriter writer) if (Declaration is not null) { - writer.AppendDeclarationReference(Declaration); + writer.Append(Declaration, isDeclaration: false); } else { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs index b44b8d605cc..21233fe5d39 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs @@ -1004,24 +1004,16 @@ public CodeScope AmbientScope() return codeWriterScope; } - internal void Append(CodeWriterDeclaration declaration) + internal void Append(CodeWriterDeclaration declaration, bool isDeclaration = true) { if (declaration.HasBeenDeclared(_scopes)) { WriteIdentifier(declaration.GetActualName(_scopes.Peek())); } - else + else if (isDeclaration) { WriteDeclaration(declaration); } - } - - internal void AppendDeclarationReference(CodeWriterDeclaration declaration) - { - if (declaration.HasBeenDeclared(_scopes)) - { - WriteIdentifier(declaration.GetActualName(_scopes.Peek())); - } else { WriteIdentifier(declaration.RequestedName); From a7642535271e031937e80ae238a824ea0340f0f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:19:05 +0000 Subject: [PATCH 5/7] refactor: rename isDeclaration to referenceOnly with default false Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/0518805d-cff5-4119-8f33-49d845ae2a38 --- .../src/Expressions/MemberExpression.cs | 2 +- .../src/Writers/CodeWriter.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs index f4487534414..ed582f3620c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/MemberExpression.cs @@ -22,7 +22,7 @@ internal override void Write(CodeWriter writer) if (Declaration is not null) { - writer.Append(Declaration, isDeclaration: false); + writer.Append(Declaration, referenceOnly: true); } else { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs index 21233fe5d39..1302db936ac 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs @@ -1004,19 +1004,19 @@ public CodeScope AmbientScope() return codeWriterScope; } - internal void Append(CodeWriterDeclaration declaration, bool isDeclaration = true) + internal void Append(CodeWriterDeclaration declaration, bool referenceOnly = false) { if (declaration.HasBeenDeclared(_scopes)) { WriteIdentifier(declaration.GetActualName(_scopes.Peek())); } - else if (isDeclaration) + else if (referenceOnly) { - WriteDeclaration(declaration); + WriteIdentifier(declaration.RequestedName); } else { - WriteIdentifier(declaration.RequestedName); + WriteDeclaration(declaration); } } From a9c4e4b3d479d51af578fe2cbdc2c17fb9081328 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:10:47 +0000 Subject: [PATCH 6/7] fix: add 'Dedup' to cspell dictionary to fix spell check CI Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/02c3444a-9edc-4a30-badc-847202de74a9 Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- cspell.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/cspell.yaml b/cspell.yaml index d855e795eea..b2bfacf4a9f 100644 --- a/cspell.yaml +++ b/cspell.yaml @@ -57,6 +57,7 @@ words: - debugpy - Declipse - dedented + - Dedup - Dedupes - deps - destructures From eb47e6cfb8f4ede2be846f0a97b1f2fff4797b22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:18:18 +0000 Subject: [PATCH 7/7] fix: rename FieldDedup test to FieldDeduplication to fix cspell, revert cspell.yaml Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/27dbf53d-323b-4a4f-a837-e82f2626ea68 Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- cspell.yaml | 1 - .../test/Writers/CodeWriterDeclarationTests.cs | 2 +- ...> FieldDeduplicationUpdatesReferencesViaMemberExpression.cs} | 0 3 files changed, 1 insertion(+), 2 deletions(-) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/{FieldDedupUpdatesReferencesViaMemberExpression.cs => FieldDeduplicationUpdatesReferencesViaMemberExpression.cs} (100%) diff --git a/cspell.yaml b/cspell.yaml index b2bfacf4a9f..d855e795eea 100644 --- a/cspell.yaml +++ b/cspell.yaml @@ -57,7 +57,6 @@ words: - debugpy - Declipse - dedented - - Dedup - Dedupes - deps - destructures diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs index 907ffcebe43..35d1f628594 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/CodeWriterDeclarationTests.cs @@ -169,7 +169,7 @@ public void ScopeDeclaredTwiceForMethodSignatureParam() } [Test] - public void FieldDedupUpdatesReferencesViaMemberExpression() + public void FieldDeduplicationUpdatesReferencesViaMemberExpression() { var type = new TestTypeProvider(); var field1 = new FieldProvider(FieldModifiers.Private | FieldModifiers.ReadOnly, typeof(string), "_scope", type); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldDedupUpdatesReferencesViaMemberExpression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldDeduplicationUpdatesReferencesViaMemberExpression.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldDedupUpdatesReferencesViaMemberExpression.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Writers/TestData/CodeWriterDeclarationTests/FieldDeduplicationUpdatesReferencesViaMemberExpression.cs