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..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 @@ -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, referenceOnly: true); + } + 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/src/Writers/CodeWriter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs index f57b5435a4d..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,12 +1004,16 @@ public CodeScope AmbientScope() return codeWriterScope; } - internal void Append(CodeWriterDeclaration declaration) + internal void Append(CodeWriterDeclaration declaration, bool referenceOnly = false) { if (declaration.HasBeenDeclared(_scopes)) { WriteIdentifier(declaration.GetActualName(_scopes.Peek())); } + else if (referenceOnly) + { + WriteIdentifier(declaration.RequestedName); + } else { WriteDeclaration(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..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 @@ -168,6 +168,35 @@ 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)); + } + + [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/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 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;