diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs index e21290f420d..14e4ddef553 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs @@ -20,6 +20,7 @@ public class ModelReaderWriterContextDefinition : TypeProvider { private const string DefaultObsoleteDiagnosticId = "CS0618"; private const string ExperimentalAttributeFullName = "System.Diagnostics.CodeAnalysis.ExperimentalAttribute"; + private static readonly CSharpType s_buildableAttributeType = new CSharpType(typeof(ModelReaderWriterBuildableAttribute)); private static readonly CSharpTypeNameComparer s_cSharpTypeNameComparer = new CSharpTypeNameComparer(); private static readonly TypeProviderTypeNameComparer s_typeProviderNameComparer = new TypeProviderTypeNameComparer(); @@ -58,9 +59,7 @@ protected override IReadOnlyList BuildAttributes() continue; } - // Use the full attribute type name to ensure proper compilation - var attributeType = new CSharpType(typeof(ModelReaderWriterBuildableAttribute)); - var attributeStatement = new AttributeStatement(attributeType, TypeOf(type)); + var attributeStatement = new AttributeStatement(s_buildableAttributeType, TypeOf(type)); string experimentalTypeJustification = $"{type} is experimental and may change in future versions."; string obsoleteTypeJustification = $"{type} is obsolete and may be removed in future versions."; @@ -79,26 +78,106 @@ protected override IReadOnlyList BuildAttributes() continue; } - // Use the full attribute type name to ensure proper compilation - var attributeType = new CSharpType(typeof(ModelReaderWriterBuildableAttribute)); - var attributeStatement = new AttributeStatement(attributeType, TypeOf(provider.Type)); - - string experimentalTypeJustification = $"{provider.Type} is experimental and may change in future versions."; - string obsoleteTypeJustification = $"{provider.Type} is obsolete and may be removed in future versions."; + var attributeStatement = new AttributeStatement(s_buildableAttributeType, TypeOf(provider.Type)); // If the type is experimental or obsolete, we add a suppression for it AddAttributeForType( attributes, attributeStatement, provider, - experimentalTypeJustification, - obsoleteTypeJustification); + provider.Type.FullyQualifiedName); } + AddLastContractBuildableAttributes(attributes, customizedBuildableTypes); + // Sort by the simple type name (last part after the last dot) instead of the fully qualified name return attributes.OrderBy(a => GetSimpleTypeName(a.Key)).Select(kvp => kvp.Value).ToList(); } + private void AddLastContractBuildableAttributes( + Dictionary attributes, + HashSet customizedBuildableTypes) + { + if (LastContractView?.Attributes is not { Count: > 0 } lastContractAttributes) + { + return; + } + + var outputLibraryProviders = new Dictionary(StringComparer.Ordinal); + foreach (var provider in ScmCodeModelGenerator.Instance.OutputLibrary.TypeProviders) + { + outputLibraryProviders.TryAdd(GetTypeIdentity(provider.Type), provider); + } + + foreach (var attribute in lastContractAttributes) + { + if (!string.Equals( + attribute.Type.FullyQualifiedName, + s_buildableAttributeType.FullyQualifiedName, + StringComparison.Ordinal)) + { + continue; + } + + var targetType = GetBuildableAttributeTargetType(attribute); + if (targetType is null) + { + continue; + } + + var identity = GetTypeIdentity(targetType); + + TypeProvider? resolvedProvider; + if (outputLibraryProviders.TryGetValue(identity, out var outputLibraryProvider)) + { + if (!ShouldWriteProvider(outputLibraryProvider)) + { + continue; + } + resolvedProvider = outputLibraryProvider; + } + else + { + resolvedProvider = ScmCodeModelGenerator.Instance.SourceInputModel.FindForTypeInCustomization( + targetType.Namespace, + targetType.ClrMetadataName, + null, + includeReferencedAssemblies: true); + + if (resolvedProvider is null) + { + continue; + } + } + + if (attributes.ContainsKey(identity) || customizedBuildableTypes.Contains(identity)) + { + continue; + } + + var newAttributeStatement = new AttributeStatement(s_buildableAttributeType, TypeOf(targetType)); + + AddAttributeForType( + attributes, + newAttributeStatement, + resolvedProvider, + identity); + } + } + + private static CSharpType? GetBuildableAttributeTargetType(AttributeStatement attribute) + { + foreach (var argument in attribute.Arguments) + { + if (argument is TypeOfExpression typeOf) + { + return typeOf.Type; + } + } + + return null; + } + private static bool IsBuildableAttribute(MethodBodyStatement statement) { var attribute = statement switch @@ -108,7 +187,7 @@ private static bool IsBuildableAttribute(MethodBodyStatement statement) _ => null }; - return attribute?.Type.Equals(typeof(ModelReaderWriterBuildableAttribute)) == true; + return attribute?.Type.Equals(s_buildableAttributeType) == true; } private HashSet GetCustomizedBuildableTypes() @@ -118,7 +197,7 @@ private HashSet GetCustomizedBuildableTypes() { if (!string.Equals( attribute.Type.FullyQualifiedName, - typeof(ModelReaderWriterBuildableAttribute).FullName, + s_buildableAttributeType.FullyQualifiedName, StringComparison.Ordinal)) { continue; @@ -525,21 +604,20 @@ private static void AddAttributeForType( Dictionary attributes, AttributeStatement attributeStatement, TypeProvider typeProvider, - string experimentalTypeJustification, - string obsoleteTypeJustification) + string key) { AttributeStatement? experimentalOrObsoleteAttribute = typeProvider.CanonicalView.Attributes .FirstOrDefault(a => a.Type.Equals(typeof(ExperimentalAttribute)) || a.Type.Equals(typeof(ObsoleteAttribute))); - var key = typeProvider.Type.FullyQualifiedName; - if (experimentalOrObsoleteAttribute?.Type.Equals(typeof(ExperimentalAttribute)) == true) { - attributes.Add(key, new SuppressionStatement(attributeStatement, experimentalOrObsoleteAttribute.Arguments[0], experimentalTypeJustification)); + string justification = $"{typeProvider.Type} is experimental and may change in future versions."; + attributes.Add(key, new SuppressionStatement(attributeStatement, experimentalOrObsoleteAttribute.Arguments[0], justification)); } else if (experimentalOrObsoleteAttribute?.Type.Equals(typeof(ObsoleteAttribute)) == true) { - attributes.Add(key, new SuppressionStatement(attributeStatement, Literal(DefaultObsoleteDiagnosticId), obsoleteTypeJustification)); + string justification = $"{typeProvider.Type} is obsolete and may be removed in future versions."; + attributes.Add(key, new SuppressionStatement(attributeStatement, Literal(DefaultObsoleteDiagnosticId), justification)); } else { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs index 5c562d371ef..4685d2e0489 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs @@ -150,6 +150,37 @@ public void RemovedProvidersDoNotContributeBuildableAttributes() } } + [Test] + public async Task RemovedProvidersAreNotRestoredFromLastContract() + { + // A provider can exist in the output library but be pruned by the reference map + // (ShouldWriteProvider == false), so it is never emitted. A last-contract buildable attribute for + // such a type must not be restored, otherwise the context would reference typeof() and + // break compilation. + var keptProvider = new TestMrwSerialization(implementsPersistableModel: true, includeDepModelProperty: false); + var removedProvider = new RemovedProviderWithFrameworkDependency(); + var outputLibrary = new TestOutputLibrary([keptProvider, removedProvider]); + var mockGenerator = MockHelpers.LoadMockGenerator(createOutputLibrary: () => outputLibrary); + mockGenerator.SetupProperty( + p => p.SourceInputModel, + new SourceInputModel(null, await Helpers.GetCompilationFromDirectoryAsync())); + + try + { + CodeModelGenerator.Instance.AddTypeToKeep(keptProvider); + ProviderReferenceMapAnalyzer.Analyze(ScmCodeModelGenerator.Instance.OutputLibrary.TypeProviders); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var writer = new TypeProviderWriter(contextDefinition); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + finally + { + ProviderReferenceMapAnalyzer.ResetPreWriteAccessibility(); + } + } + [Test] public async Task VisitorAttributesArePreservedAfterReferenceMapAnalysis() { @@ -1840,6 +1871,314 @@ public async Task CustomizedBuildableAttributesAreNotRegenerated() "Buildable attributes supplied by a customized context should not be regenerated"); } + [Test] + public async Task LastContractBuildableAttributesAreRestoredWhenMissing() + { + // The last contract declared buildable attributes for RegularModel, RestoredType, and RemovedModel. + // RegularModel is emitted by the current generation. RestoredType still exists in the output library + // but is not itself emitted as a buildable attribute (an enum here), so it must be restored for + // back-compat. RemovedModel no longer exists in the output library and must not be restored, since + // restoring it would emit typeof() and break compilation. + var regularModel = InputFactory.Model("RegularModel", properties: + [ + InputFactory.Property("Property1", InputPrimitiveType.String) + ]); + + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => [regularModel], + inputEnums: () => [InputFactory.Int32Enum("RestoredType", [("Value1", 1)])], + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + var regularModelCount = buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RegularModel")); + var restoredTypeCount = buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RestoredType")); + var removedModelCount = buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RemovedModel")); + + Assert.AreEqual(1, regularModelCount, + "RegularModel is produced by the current generation and must not be duplicated by the last contract entry"); + Assert.AreEqual(1, restoredTypeCount, + "RestoredType is still part of the output library and must be restored for back-compat"); + Assert.AreEqual(0, removedModelCount, + "RemovedModel is no longer part of the output library and must not be restored"); + + var writer = new TypeProviderWriter(contextDefinition); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public async Task BuildAttributesForBackCompatibilityDeduplicatesAcrossGeneratedCustomAndLastContractBuildableAttributes() + { + // RegularModel is produced by the current generation, CustomModel is supplied by customized code, and + // the last contract declares buildable attributes for RegularModel, CustomModel, RestoredType, and + // RemovedModel. RestoredType is still in the output library but not emitted as a buildable attribute, + // so it is restored. RemovedModel is no longer in the output library and must not be restored, while + // the entries already produced by the generation or customized code must not be duplicated. + var regularModel = InputFactory.Model("RegularModel", properties: + [ + InputFactory.Property("Property1", InputPrimitiveType.String) + ]); + + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => [regularModel], + inputEnums: () => [InputFactory.Int32Enum("RestoredType", [("Value1", 1)])], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Last")); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + var regularModelCount = buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RegularModel")); + var customModelCount = buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("CustomModel")); + var restoredTypeCount = buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RestoredType")); + var removedModelCount = buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RemovedModel")); + + Assert.AreEqual(1, regularModelCount, + "RegularModel is produced by the current generation and must appear exactly once"); + Assert.AreEqual(0, customModelCount, + "CustomModel is supplied by customized code and must not be regenerated from the last contract"); + Assert.AreEqual(1, restoredTypeCount, + "RestoredType is still part of the output library and must be restored for back-compat"); + Assert.AreEqual(0, removedModelCount, + "RemovedModel is no longer part of the output library and must not be restored"); + + var writer = new TypeProviderWriter(contextDefinition); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public async Task BuildAttributesForBackCompatibilityIncludesGeneratedCustomAndRestoredLastContractBuildableAttributes() + { + // GeneratedModelA and GeneratedModelB are produced by the current generation, CustomModel is supplied + // by customized code, and the last contract additionally declares RestoredTypeA, RestoredTypeB, + // RemovedModelA, and RemovedModelB. The generated entries are emitted, the customized entry is left to + // the customized code, the restored types are still in the output library and are restored, and the + // removed types are no longer in the output library and must not be restored. + var generatedModelA = InputFactory.Model("GeneratedModelA", properties: + [ + InputFactory.Property("Property1", InputPrimitiveType.String) + ]); + var generatedModelB = InputFactory.Model("GeneratedModelB", properties: + [ + InputFactory.Property("Property2", InputPrimitiveType.String) + ]); + + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => [generatedModelA, generatedModelB], + inputEnums: () => + [ + InputFactory.Int32Enum("RestoredTypeA", [("Value1", 1)]), + InputFactory.Int32Enum("RestoredTypeB", [("Value1", 1)]) + ], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Last")); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("GeneratedModelA")), + "GeneratedModelA is produced by the current generation and must appear exactly once"); + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("GeneratedModelB")), + "GeneratedModelB is produced by the current generation and must appear exactly once"); + Assert.AreEqual(0, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("CustomModel")), + "CustomModel is supplied by customized code and must not be regenerated from the last contract"); + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RestoredTypeA")), + "RestoredTypeA is still part of the output library and must be restored for back-compat"); + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RestoredTypeB")), + "RestoredTypeB is still part of the output library and must be restored for back-compat"); + Assert.AreEqual(0, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RemovedModelA")), + "RemovedModelA is no longer part of the output library and must not be restored"); + Assert.AreEqual(0, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RemovedModelB")), + "RemovedModelB is no longer part of the output library and must not be restored"); + + var writer = new TypeProviderWriter(contextDefinition); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public async Task LastContractBuildableAttributesForReferencedAssemblyTypesAreRestored() + { + // The last contract declared a buildable attribute for BinaryData, which lives in a referenced + // assembly rather than the generated output. Because the type still resolves through the generated + // code's references, its attribute must be restored for back-compat + var regularModel = InputFactory.Model("RegularModel", properties: + [ + InputFactory.Property("Property1", InputPrimitiveType.String) + ]); + + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => [regularModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RegularModel")), + "RegularModel is produced by the current generation and must appear exactly once"); + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("BinaryData")), + "BinaryData lives in a referenced assembly and must be restored for back-compat"); + + var writer = new TypeProviderWriter(contextDefinition); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public async Task LastContractBuildableAttributesForTypesNotInAssemblyAreNotRestored() + { + // The last contract declared a buildable attribute for RemovedModel, which is not produced by the + // current generation and does not resolve through the generated code's references. It must not be + // restored, since emitting typeof() would break compilation. + var regularModel = InputFactory.Model("RegularModel", properties: + [ + InputFactory.Property("Property1", InputPrimitiveType.String) + ]); + + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => [regularModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RegularModel")), + "RegularModel is produced by the current generation and must appear exactly once"); + Assert.AreEqual(0, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RemovedModel")), + "RemovedModel is not part of the generated assembly and must not be restored"); + + var writer = new TypeProviderWriter(contextDefinition); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public async Task LastContractBuildableAttributesForObsoleteCustomTypeHaveSuppression() + { + // The last contract declared a buildable attribute for ObsoleteCustomModel. The type is defined + // only in the customization layer (not produced by the current generation) with [Obsolete]. + // The restored attribute must be wrapped in #pragma warning disable CS0618 suppressions. + var regularModel = InputFactory.Model("RegularModel", properties: + [ + InputFactory.Property("Property1", InputPrimitiveType.String) + ]); + + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => [regularModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RegularModel")), + "RegularModel is produced by the current generation and must appear exactly once"); + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("ObsoleteCustomModel")), + "ObsoleteCustomModel is in the customization layer and must be restored for back-compat"); + + var writer = new TypeProviderWriter(contextDefinition); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public async Task LastContractBuildableAttributesForExperimentalCustomTypeHaveSuppression() + { + // The last contract declared a buildable attribute for ExperimentalCustomModel. The type is defined + // only in the customization layer (not produced by the current generation) with [Experimental]. + // The restored attribute must be wrapped in #pragma warning disable suppressions. + var regularModel = InputFactory.Model("RegularModel", properties: + [ + InputFactory.Property("Property1", InputPrimitiveType.String) + ]); + + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => [regularModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RegularModel")), + "RegularModel is produced by the current generation and must appear exactly once"); + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("ExperimentalCustomModel")), + "ExperimentalCustomModel is in the customization layer and must be restored for back-compat"); + + var writer = new TypeProviderWriter(contextDefinition); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public async Task LastContractBuildableAttributesForExperimentalAndObsoleteCustomTypeHaveSuppression() + { + // The last contract declared a buildable attribute for ExperimentalObsoleteModel. The type is defined + // only in the customization layer with both [Experimental] and [Obsolete]. + // When both attributes are present, [Experimental] (declared first) takes precedence and the + // restored attribute must be wrapped in experimental suppressions. + var regularModel = InputFactory.Model("RegularModel", properties: + [ + InputFactory.Property("Property1", InputPrimitiveType.String) + ]); + + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => [regularModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("RegularModel")), + "RegularModel is produced by the current generation and must appear exactly once"); + Assert.AreEqual(1, buildableAttributes + .Count(a => a.Arguments.First().ToDisplayString().Contains("ExperimentalObsoleteModel")), + "ExperimentalObsoleteModel is in the customization layer and must be restored for back-compat"); + + var writer = new TypeProviderWriter(contextDefinition); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + // Buildable attributes restored from the last contract are symbol-based (IsFrameworkType == false), so + // match by fully qualified name to cover both generated and restored entries. + private static List GetBuildableAttributes(ModelReaderWriterContextDefinition contextDefinition) + => contextDefinition.Attributes + .Where(a => string.Equals( + a.Type.FullyQualifiedName, + typeof(ModelReaderWriterBuildableAttribute).FullName, + StringComparison.Ordinal)) + .ToList(); + [Test] public async Task CustomProjectionPropertiesDoNotAddBuildableTypes() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityDeduplicatesAcrossGeneratedCustomAndLastContractBuildableAttributes(Custom)/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityDeduplicatesAcrossGeneratedCustomAndLastContractBuildableAttributes(Custom)/SampleContext.cs new file mode 100644 index 00000000000..d08a220f253 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityDeduplicatesAcrossGeneratedCustomAndLastContractBuildableAttributes(Custom)/SampleContext.cs @@ -0,0 +1,16 @@ +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Sample.Models.CustomModel))] + public partial class SampleContext + { + } +} + +namespace Sample.Models +{ + public partial class CustomModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityDeduplicatesAcrossGeneratedCustomAndLastContractBuildableAttributes(Last)/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityDeduplicatesAcrossGeneratedCustomAndLastContractBuildableAttributes(Last)/SampleContext.cs new file mode 100644 index 00000000000..0d9da3b87e8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityDeduplicatesAcrossGeneratedCustomAndLastContractBuildableAttributes(Last)/SampleContext.cs @@ -0,0 +1,31 @@ +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Sample.Models.RegularModel))] + [ModelReaderWriterBuildable(typeof(Sample.Models.CustomModel))] + [ModelReaderWriterBuildable(typeof(Sample.Models.RestoredType))] + [ModelReaderWriterBuildable(typeof(Sample.Models.RemovedModel))] + public partial class SampleContext + { + } +} + +namespace Sample.Models +{ + public partial class RegularModel + { + } + + public partial class CustomModel + { + } + + public enum RestoredType + { + } + + public partial class RemovedModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityDeduplicatesAcrossGeneratedCustomAndLastContractBuildableAttributes.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityDeduplicatesAcrossGeneratedCustomAndLastContractBuildableAttributes.cs new file mode 100644 index 00000000000..b98dd8f5e4b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityDeduplicatesAcrossGeneratedCustomAndLastContractBuildableAttributes.cs @@ -0,0 +1,15 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Sample.Models; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.RegularModel))] + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.RestoredType))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityIncludesGeneratedCustomAndRestoredLastContractBuildableAttributes(Custom)/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityIncludesGeneratedCustomAndRestoredLastContractBuildableAttributes(Custom)/SampleContext.cs new file mode 100644 index 00000000000..d08a220f253 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityIncludesGeneratedCustomAndRestoredLastContractBuildableAttributes(Custom)/SampleContext.cs @@ -0,0 +1,16 @@ +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Sample.Models.CustomModel))] + public partial class SampleContext + { + } +} + +namespace Sample.Models +{ + public partial class CustomModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityIncludesGeneratedCustomAndRestoredLastContractBuildableAttributes(Last)/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityIncludesGeneratedCustomAndRestoredLastContractBuildableAttributes(Last)/SampleContext.cs new file mode 100644 index 00000000000..a3b804b6fd1 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityIncludesGeneratedCustomAndRestoredLastContractBuildableAttributes(Last)/SampleContext.cs @@ -0,0 +1,46 @@ +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Sample.Models.GeneratedModelA))] + [ModelReaderWriterBuildable(typeof(Sample.Models.GeneratedModelB))] + [ModelReaderWriterBuildable(typeof(Sample.Models.CustomModel))] + [ModelReaderWriterBuildable(typeof(Sample.Models.RestoredTypeA))] + [ModelReaderWriterBuildable(typeof(Sample.Models.RestoredTypeB))] + [ModelReaderWriterBuildable(typeof(Sample.Models.RemovedModelA))] + [ModelReaderWriterBuildable(typeof(Sample.Models.RemovedModelB))] + public partial class SampleContext + { + } +} + +namespace Sample.Models +{ + public partial class GeneratedModelA + { + } + + public partial class GeneratedModelB + { + } + + public partial class CustomModel + { + } + + public enum RestoredTypeA + { + } + + public enum RestoredTypeB + { + } + + public partial class RemovedModelA + { + } + + public partial class RemovedModelB + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityIncludesGeneratedCustomAndRestoredLastContractBuildableAttributes.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityIncludesGeneratedCustomAndRestoredLastContractBuildableAttributes.cs new file mode 100644 index 00000000000..27c67df96f5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityIncludesGeneratedCustomAndRestoredLastContractBuildableAttributes.cs @@ -0,0 +1,17 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Sample.Models; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.GeneratedModelA))] + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.GeneratedModelB))] + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.RestoredTypeA))] + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.RestoredTypeB))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesAreRestoredWhenMissing.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesAreRestoredWhenMissing.cs new file mode 100644 index 00000000000..b98dd8f5e4b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesAreRestoredWhenMissing.cs @@ -0,0 +1,15 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Sample.Models; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.RegularModel))] + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.RestoredType))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesAreRestoredWhenMissing/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesAreRestoredWhenMissing/SampleContext.cs new file mode 100644 index 00000000000..73f9e3333f6 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesAreRestoredWhenMissing/SampleContext.cs @@ -0,0 +1,26 @@ +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Sample.Models.RegularModel))] + [ModelReaderWriterBuildable(typeof(Sample.Models.RestoredType))] + [ModelReaderWriterBuildable(typeof(Sample.Models.RemovedModel))] + public partial class SampleContext + { + } +} + +namespace Sample.Models +{ + public partial class RegularModel + { + } + + public enum RestoredType + { + } + + public partial class RemovedModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalAndObsoleteCustomTypeHaveSuppression(Custom)/ExperimentalObsoleteModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalAndObsoleteCustomTypeHaveSuppression(Custom)/ExperimentalObsoleteModel.cs new file mode 100644 index 00000000000..544c6776fec --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalAndObsoleteCustomTypeHaveSuppression(Custom)/ExperimentalObsoleteModel.cs @@ -0,0 +1,11 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Sample.Models +{ + [Experimental("TEST002")] + [Obsolete("This type is obsolete.")] + public class ExperimentalObsoleteModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalAndObsoleteCustomTypeHaveSuppression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalAndObsoleteCustomTypeHaveSuppression.cs new file mode 100644 index 00000000000..3bad5fe990f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalAndObsoleteCustomTypeHaveSuppression.cs @@ -0,0 +1,17 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Sample.Models; + +namespace Sample +{ +#pragma warning disable TEST002 // global::Sample.Models.ExperimentalObsoleteModel is experimental and may change in future versions. + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.ExperimentalObsoleteModel))] +#pragma warning restore TEST002 // global::Sample.Models.ExperimentalObsoleteModel is experimental and may change in future versions. + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.RegularModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalAndObsoleteCustomTypeHaveSuppression/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalAndObsoleteCustomTypeHaveSuppression/SampleContext.cs new file mode 100644 index 00000000000..fa84b3ba47c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalAndObsoleteCustomTypeHaveSuppression/SampleContext.cs @@ -0,0 +1,21 @@ +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Sample.Models.RegularModel))] + [ModelReaderWriterBuildable(typeof(Sample.Models.ExperimentalObsoleteModel))] + public partial class SampleContext + { + } +} + +namespace Sample.Models +{ + public partial class RegularModel + { + } + + public class ExperimentalObsoleteModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalCustomTypeHaveSuppression(Custom)/ExperimentalCustomModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalCustomTypeHaveSuppression(Custom)/ExperimentalCustomModel.cs new file mode 100644 index 00000000000..545b4f63df8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalCustomTypeHaveSuppression(Custom)/ExperimentalCustomModel.cs @@ -0,0 +1,9 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Sample.Models +{ + [Experimental("TEST001")] + public class ExperimentalCustomModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalCustomTypeHaveSuppression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalCustomTypeHaveSuppression.cs new file mode 100644 index 00000000000..2e9a1097389 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalCustomTypeHaveSuppression.cs @@ -0,0 +1,17 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Sample.Models; + +namespace Sample +{ +#pragma warning disable TEST001 // global::Sample.Models.ExperimentalCustomModel is experimental and may change in future versions. + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.ExperimentalCustomModel))] +#pragma warning restore TEST001 // global::Sample.Models.ExperimentalCustomModel is experimental and may change in future versions. + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.RegularModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalCustomTypeHaveSuppression/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalCustomTypeHaveSuppression/SampleContext.cs new file mode 100644 index 00000000000..58a42a4d7d2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForExperimentalCustomTypeHaveSuppression/SampleContext.cs @@ -0,0 +1,21 @@ +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Sample.Models.RegularModel))] + [ModelReaderWriterBuildable(typeof(Sample.Models.ExperimentalCustomModel))] + public partial class SampleContext + { + } +} + +namespace Sample.Models +{ + public partial class RegularModel + { + } + + public class ExperimentalCustomModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForObsoleteCustomTypeHaveSuppression(Custom)/ObsoleteCustomModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForObsoleteCustomTypeHaveSuppression(Custom)/ObsoleteCustomModel.cs new file mode 100644 index 00000000000..bbdf7f72bb7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForObsoleteCustomTypeHaveSuppression(Custom)/ObsoleteCustomModel.cs @@ -0,0 +1,9 @@ +using System; + +namespace Sample.Models +{ + [Obsolete("This type is obsolete.")] + public class ObsoleteCustomModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForObsoleteCustomTypeHaveSuppression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForObsoleteCustomTypeHaveSuppression.cs new file mode 100644 index 00000000000..acfdcd30aca --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForObsoleteCustomTypeHaveSuppression.cs @@ -0,0 +1,17 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Sample.Models; + +namespace Sample +{ +#pragma warning disable CS0618 // global::Sample.Models.ObsoleteCustomModel is obsolete and may be removed in future versions. + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.ObsoleteCustomModel))] +#pragma warning restore CS0618 // global::Sample.Models.ObsoleteCustomModel is obsolete and may be removed in future versions. + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.RegularModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForObsoleteCustomTypeHaveSuppression/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForObsoleteCustomTypeHaveSuppression/SampleContext.cs new file mode 100644 index 00000000000..2248e2419a7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForObsoleteCustomTypeHaveSuppression/SampleContext.cs @@ -0,0 +1,23 @@ +using System; +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Sample.Models.RegularModel))] + [ModelReaderWriterBuildable(typeof(Sample.Models.ObsoleteCustomModel))] + public partial class SampleContext + { + } +} + +namespace Sample.Models +{ + public partial class RegularModel + { + } + + [Obsolete("This type is obsolete.")] + public class ObsoleteCustomModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForReferencedAssemblyTypesAreRestored(Custom)/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForReferencedAssemblyTypesAreRestored(Custom)/SampleContext.cs new file mode 100644 index 00000000000..355e0b94b10 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForReferencedAssemblyTypesAreRestored(Custom)/SampleContext.cs @@ -0,0 +1,8 @@ +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class SampleContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForReferencedAssemblyTypesAreRestored.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForReferencedAssemblyTypesAreRestored.cs new file mode 100644 index 00000000000..e5fda83fdd8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForReferencedAssemblyTypesAreRestored.cs @@ -0,0 +1,16 @@ +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using Sample.Models; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::System.BinaryData))] + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.RegularModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForReferencedAssemblyTypesAreRestored/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForReferencedAssemblyTypesAreRestored/SampleContext.cs new file mode 100644 index 00000000000..017736ee106 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForReferencedAssemblyTypesAreRestored/SampleContext.cs @@ -0,0 +1,18 @@ +using System; +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Sample.Models.RegularModel))] + [ModelReaderWriterBuildable(typeof(System.BinaryData))] + public partial class SampleContext + { + } +} + +namespace Sample.Models +{ + public partial class RegularModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForTypesNotInAssemblyAreNotRestored(Custom)/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForTypesNotInAssemblyAreNotRestored(Custom)/SampleContext.cs new file mode 100644 index 00000000000..355e0b94b10 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForTypesNotInAssemblyAreNotRestored(Custom)/SampleContext.cs @@ -0,0 +1,8 @@ +using System.ClientModel.Primitives; + +namespace Sample +{ + public partial class SampleContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForTypesNotInAssemblyAreNotRestored.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForTypesNotInAssemblyAreNotRestored.cs new file mode 100644 index 00000000000..e560fba19ea --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForTypesNotInAssemblyAreNotRestored.cs @@ -0,0 +1,14 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Sample.Models; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.RegularModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForTypesNotInAssemblyAreNotRestored/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForTypesNotInAssemblyAreNotRestored/SampleContext.cs new file mode 100644 index 00000000000..f754f58cf60 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/LastContractBuildableAttributesForTypesNotInAssemblyAreNotRestored/SampleContext.cs @@ -0,0 +1,21 @@ +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Sample.Models.RegularModel))] + [ModelReaderWriterBuildable(typeof(Sample.Models.RemovedModel))] + public partial class SampleContext + { + } +} + +namespace Sample.Models +{ + public partial class RegularModel + { + } + + public partial class RemovedModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/RemovedProvidersAreNotRestoredFromLastContract.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/RemovedProvidersAreNotRestoredFromLastContract.cs new file mode 100644 index 00000000000..29d6675052c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/RemovedProvidersAreNotRestoredFromLastContract.cs @@ -0,0 +1,13 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.TestMrwSerialization))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/RemovedProvidersAreNotRestoredFromLastContract/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/RemovedProvidersAreNotRestoredFromLastContract/SampleContext.cs new file mode 100644 index 00000000000..8ad09cdcbb8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/RemovedProvidersAreNotRestoredFromLastContract/SampleContext.cs @@ -0,0 +1,18 @@ +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Sample.TestMrwSerialization))] + [ModelReaderWriterBuildable(typeof(Sample.RemovedProviderWithFrameworkDependency))] + public partial class SampleContext + { + } + + public partial class TestMrwSerialization + { + } + + internal partial class RemovedProviderWithFrameworkDependency + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/LiteralExpression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/LiteralExpression.cs index bb67f001301..0b6f139f1c7 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/LiteralExpression.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/LiteralExpression.cs @@ -19,7 +19,13 @@ internal override void Write(CodeWriter writer) null => "null", string s => SyntaxFactory.Literal(s).ToString(), int i => SyntaxFactory.Literal(i).ToString(), + uint ui => SyntaxFactory.Literal(ui).ToString(), long l => SyntaxFactory.Literal(l).ToString(), + ulong ul => SyntaxFactory.Literal(ul).ToString(), + byte b => SyntaxFactory.Literal((int)b).ToString(), + sbyte sb => SyntaxFactory.Literal((int)sb).ToString(), + short s => SyntaxFactory.Literal((int)s).ToString(), + ushort us => SyntaxFactory.Literal((uint)us).ToString(), decimal d => SyntaxFactory.Literal(d).ToString(), double d => SyntaxFactory.Literal(d).ToString(), float f => SyntaxFactory.Literal(f).ToString(), diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/CSharpType.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/CSharpType.cs index f2bc01a85e0..2ec3c86dc41 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/CSharpType.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/CSharpType.cs @@ -42,6 +42,7 @@ public class CSharpType private bool? _isIAsyncEnumerableOfT; private bool? _containsBinaryData; private int? _hashCode; + private string? _clrMetadataName; private CSharpType? _propertyInitializationType; private CSharpType? _elementType; private CSharpType? _inputType; @@ -181,6 +182,22 @@ internal CSharpType( ? $"{Namespace}.{Name}" : $"{Namespace}.{DeclaringType.Name}.{Name}"; public CSharpType? DeclaringType { get; private init; } + + /// + /// Gets the CLR metadata name for this type, including the arity suffix for generic types + /// (e.g., Type`1) and the +-separated declaring-type chain for nested types + /// (e.g., Outer`1+Inner). This format is compatible with + /// . + /// + public string ClrMetadataName => _clrMetadataName ??= BuildClrMetadataName(); + + private string BuildClrMetadataName() + { + var simpleName = Arguments.Count > 0 ? $"{Name}`{Arguments.Count}" : Name; + return DeclaringType is null + ? simpleName + : $"{DeclaringType.ClrMetadataName}+{simpleName}"; + } public bool IsValueType { get; private init; } public bool IsEnum => _underlyingType is not null; public bool IsLiteral => _literal is not null; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Expressions/LiteralExpressionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Expressions/LiteralExpressionTests.cs new file mode 100644 index 00000000000..5d4d43db3fa --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Expressions/LiteralExpressionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.TypeSpec.Generator.Expressions; +using NUnit.Framework; + +namespace Microsoft.TypeSpec.Generator.Tests.Expressions +{ + internal class LiteralExpressionTests + { + [TestCase((uint)42, "42U")] + [TestCase(uint.MaxValue, "4294967295U")] + public void Write_UInt(uint value, string expected) + { + var expression = new LiteralExpression(value); + using var writer = new CodeWriter(); + expression.Write(writer); + + Assert.AreEqual(expected, writer.ToString(false)); + } + + [TestCase((ulong)42, "42UL")] + [TestCase(ulong.MaxValue, "18446744073709551615UL")] + public void Write_ULong(ulong value, string expected) + { + var expression = new LiteralExpression(value); + using var writer = new CodeWriter(); + expression.Write(writer); + + Assert.AreEqual(expected, writer.ToString(false)); + } + + [TestCase((byte)0, "0")] + [TestCase((byte)42, "42")] + [TestCase(byte.MaxValue, "255")] + public void Write_Byte(byte value, string expected) + { + var expression = new LiteralExpression(value); + using var writer = new CodeWriter(); + expression.Write(writer); + + Assert.AreEqual(expected, writer.ToString(false)); + } + + [TestCase((sbyte)42, "42")] + [TestCase((sbyte)-42, "-42")] + [TestCase(sbyte.MinValue, "-128")] + public void Write_SByte(sbyte value, string expected) + { + var expression = new LiteralExpression(value); + using var writer = new CodeWriter(); + expression.Write(writer); + + Assert.AreEqual(expected, writer.ToString(false)); + } + + [TestCase((short)42, "42")] + [TestCase((short)-42, "-42")] + [TestCase(short.MinValue, "-32768")] + public void Write_Short(short value, string expected) + { + var expression = new LiteralExpression(value); + using var writer = new CodeWriter(); + expression.Write(writer); + + Assert.AreEqual(expected, writer.ToString(false)); + } + + [TestCase((ushort)42, "42U")] + [TestCase(ushort.MaxValue, "65535U")] + public void Write_UShort(ushort value, string expected) + { + var expression = new LiteralExpression(value); + using var writer = new CodeWriter(); + expression.Write(writer); + + Assert.AreEqual(expected, writer.ToString(false)); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Primitives/CSharpTypeTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Primitives/CSharpTypeTests.cs index eb18e3e8be0..f1ad231f280 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Primitives/CSharpTypeTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Primitives/CSharpTypeTests.cs @@ -661,5 +661,56 @@ private class TestDerivedType : TestBaseType private class TestBaseType { } + + [TestCase("MyNs", "SimpleType", ExpectedResult = "SimpleType")] + [TestCase("MyNs", "SimpleType`1", ExpectedResult = "SimpleType`1")] + public string ClrMetadataName_SimpleType(string ns, string name) + { + var type = new CSharpType(name, ns, false, false, null, [], true, false); + return type.ClrMetadataName; + } + + [Test] + public void ClrMetadataName_GenericType_OneTypeArg() + { + var argType = new CSharpType("T", "MyNs", false, false, null, [], true, false); + var type = new CSharpType("GenericType", "MyNs", false, false, null, [argType], true, false); + Assert.AreEqual("GenericType`1", type.ClrMetadataName); + } + + [Test] + public void ClrMetadataName_GenericType_TwoTypeArgs() + { + var arg1 = new CSharpType("TKey", "MyNs", false, false, null, [], true, false); + var arg2 = new CSharpType("TValue", "MyNs", false, false, null, [], true, false); + var type = new CSharpType("DictionaryType", "MyNs", false, false, null, [arg1, arg2], true, false); + Assert.AreEqual("DictionaryType`2", type.ClrMetadataName); + } + + [Test] + public void ClrMetadataName_NestedType() + { + var outerType = new CSharpType("Outer", "MyNs", false, false, null, [], true, false); + var innerType = new CSharpType("Inner", "MyNs", false, false, outerType, [], true, false); + Assert.AreEqual("Outer+Inner", innerType.ClrMetadataName); + } + + [Test] + public void ClrMetadataName_GenericNestedType() + { + var arg = new CSharpType("T", "MyNs", false, false, null, [], true, false); + var outerType = new CSharpType("Outer", "MyNs", false, false, null, [arg], true, false); + var innerType = new CSharpType("Inner", "MyNs", false, false, outerType, [], true, false); + Assert.AreEqual("Outer`1+Inner", innerType.ClrMetadataName); + } + + [Test] + public void ClrMetadataName_LazilyComputedAndCached() + { + var type = new CSharpType("MyType", "MyNs", false, false, null, [], true, false); + var first = type.ClrMetadataName; + var second = type.ClrMetadataName; + Assert.AreSame(first, second); + } } }