Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,10 @@ public static EnumProvider Create(InputEnumType input, TypeProvider? declaringTy
? new ApiVersionEnumProvider(input, declaringType)
: new FixedEnumProvider(input, declaringType);
var extensibleEnumProvider = new ExtensibleEnumProvider(input, declaringType);
fixedEnumProvider.ExtensibleEnumView = extensibleEnumProvider;
extensibleEnumProvider.FixedEnumView = fixedEnumProvider;

// Check to see if there is custom code that customizes the enum.
var customCodeView = fixedEnumProvider.CustomCodeView ?? extensibleEnumProvider.CustomCodeView;

EnumProvider provider = customCodeView switch
{
{ Type: { IsValueType: true, IsStruct: true } } => extensibleEnumProvider,
{ Type: { IsValueType: true, IsStruct: false } } => fixedEnumProvider,
_ => input.IsExtensible ? extensibleEnumProvider : fixedEnumProvider
};

if (input.Access == "public")
{
CodeModelGenerator.Instance.AddTypeToKeep(provider);
}

return provider;
return input.IsExtensible ? extensibleEnumProvider : fixedEnumProvider;
}

protected EnumProvider(InputEnumType? input)
Expand All @@ -47,6 +34,9 @@ protected EnumProvider(InputEnumType? input)
IsExtensible = input?.IsExtensible ?? false;
}

internal EnumProvider? FixedEnumView { get; set; }
internal EnumProvider? ExtensibleEnumView { get; set; }

public bool IsExtensible { get; }
private bool? _isIntValue;
internal bool IsIntValueType => _isIntValue ??= EnumUnderlyingType.Equals(typeof(int)) || EnumUnderlyingType.Equals(typeof(long));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal ExtensibleEnumProvider(InputEnumType input, TypeProvider? declaringType

_valueField = new FieldProvider(FieldModifiers.Private | FieldModifiers.ReadOnly, EnumUnderlyingType, "_value", this);
_declaringType = declaringType;
ExtensibleEnumView = this;
}

private readonly FieldProvider _valueField;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public FixedEnumProvider(InputEnumType? input, TypeProvider? declaringType) : ba

_declaringTypeProvider = declaringType;
AllowedValues = input?.Values ?? [];
FixedEnumView = this;
}

internal IReadOnlyList<InputEnumTypeValue> AllowedValues { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.TypeSpec.Generator.EmitterRpc;
using Microsoft.TypeSpec.Generator.Expressions;
using Microsoft.TypeSpec.Generator.Input;
using Microsoft.TypeSpec.Generator.Primitives;
using Microsoft.TypeSpec.Generator.SourceInput;
using Microsoft.TypeSpec.Generator.Statements;
using Microsoft.TypeSpec.Generator.Utilities;

namespace Microsoft.TypeSpec.Generator.Providers
{
Expand Down Expand Up @@ -187,7 +189,10 @@ private TypeSignatureModifiers BuildDeclarationModifiersInternal()
// mask & (mask - 1) gives us 0 if mask is a power of 2, it means we have exactly one flag of above when the mask is a power of 2
if ((mask & (mask - 1)) != 0)
{
throw new InvalidOperationException($"Invalid modifier {modifiers} on TypeProvider {Name}");
CodeModelGenerator.Instance.Emitter.ReportDiagnostic(
DiagnosticCodes.InvalidAccessModifier,
$"Invalid modifiers {modifiers} detected.",
severity: EmitterDiagnosticSeverity.Warning);
}

// we always add partial when possible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,34 @@ protected internal TypeFactory()
foreach (var visitor in Visitors)
{
enumProvider = visitor.PreVisitEnum(enumType, enumProvider);
// visit the linked enum variants
if (enumProvider is FixedEnumProvider)
{
enumProvider.ExtensibleEnumView = visitor.PreVisitEnum(enumType, enumProvider.ExtensibleEnumView);
}
else if (enumProvider is ExtensibleEnumProvider)
{
enumProvider.FixedEnumView = visitor.PreVisitEnum(enumType, enumProvider.FixedEnumView);
}
}

if (enumProvider == null)
{
EnumCache.TryAdd(enumCacheKey, null);
return null;
}

// Check to see if there is custom code that customizes the enum
enumProvider = enumProvider.CustomCodeView switch
{
{ Type: { IsValueType: true, IsStruct: true } } => enumProvider.ExtensibleEnumView ?? enumProvider,
{ Type: { IsValueType: true, IsStruct: false } } => enumProvider.FixedEnumView ?? enumProvider,
_ => enumProvider,
};

if (enumType.Access == "public")
{
CodeModelGenerator.Instance.AddTypeToKeep(enumProvider);
}

EnumCache.Add(enumCacheKey, enumProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ namespace Microsoft.TypeSpec.Generator.Utilities
internal static class DiagnosticCodes
{
public const string BaselineContractMissing = "baseline-contract-missing";
public const string InvalidAccessModifier = "invalid-access-modifier";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ public void PreVisitsEnum()

_mockVisitor.Object.VisitLibrary(_mockGenerator.Object.OutputLibrary);

_mockVisitor.Protected().Verify<TypeProvider>("PreVisitEnum", Times.Once(), inputEnum, ItExpr.Is<EnumProvider>(m => m.Name == EnumProvider.Create(inputEnum, null).Name));
// enum is visited twice, one additional time for the variant created for extensible enums
_mockVisitor.Protected().Verify<TypeProvider>("PreVisitEnum", Times.Exactly(2), inputEnum, ItExpr.Is<EnumProvider>(m => m.Name == EnumProvider.Create(inputEnum, null).Name));
}

[Test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,9 +621,11 @@ public async Task CanChangeEnumToExtensibleEnum()
[("val1", 1), ("val2", 2), ("val3", 3)],
isExtensible: false
);
var enumProvider = EnumProvider.Create(inputEnum);
var enumProvider = CodeModelGenerator.Instance.TypeFactory.CreateEnum(inputEnum);

Assert.IsTrue(enumProvider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public | TypeSignatureModifiers.Partial | TypeSignatureModifiers.Struct | TypeSignatureModifiers.ReadOnly));
Assert.IsNotNull(enumProvider);
Assert.IsTrue(enumProvider is ExtensibleEnumProvider);
Assert.IsTrue(enumProvider!.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public | TypeSignatureModifiers.Partial | TypeSignatureModifiers.Struct | TypeSignatureModifiers.ReadOnly));
}

[Test]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#nullable disable

namespace Sample.SomeOtherNamespace
{
public readonly partial struct CustomizedEnum
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
using System;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.TypeSpec.Generator.Input;
using Microsoft.TypeSpec.Generator.Primitives;
using Microsoft.TypeSpec.Generator.Providers;
using Microsoft.TypeSpec.Generator.Tests.Common;
using NUnit.Framework;

Expand Down Expand Up @@ -191,5 +193,113 @@ public void GetCleanNameSpace_ConvertsToPascalCase(string input, string expected
var actual = CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(input);
Assert.AreEqual(expected, actual);
}

[Test]
public async Task CreateEnum_WithVisitor_ChangesNamespaceToModels()
{
// Arrange - Create a fixed enum
var input = InputFactory.StringEnum(
"TestEnum",
[("value1", "value1"), ("value2", "value2")],
usage: InputModelTypeUsage.Input,
isExtensible: false);

await MockHelpers.LoadMockGeneratorAsync(inputEnumTypes: [input]);

// Create a visitor that modifies the namespace
var visitor = new NamespaceModifyingVisitor();
CodeModelGenerator.Instance.AddVisitor(visitor);

var enumProvider = CodeModelGenerator.Instance.TypeFactory.CreateEnum(input);

Assert.IsNotNull(enumProvider);
Assert.IsTrue(enumProvider!.Type.Namespace.EndsWith(".SomeOtherNamespace"));
Assert.IsTrue(enumProvider is FixedEnumProvider);
Assert.IsNotNull(enumProvider.ExtensibleEnumView);
Assert.IsNull(enumProvider.CustomCodeView);
}

[Test]
public async Task CreateEnum_WithCustomCodeAsExtensible_ReturnsExtensibleEnum()
{
// Arrange - Create a fixed enum in input
var inputEnum = InputFactory.StringEnum(
"CustomizedEnum",
[("value1", "value1"), ("value2", "value2")],
usage: InputModelTypeUsage.Input,
isExtensible: false);

// Load compilation with custom code that changes the enum to extensible (struct)
await MockHelpers.LoadMockGeneratorAsync(
inputEnumTypes: [inputEnum],
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

// Create a visitor that modifies the namespace
var visitor = new NamespaceModifyingVisitor();
CodeModelGenerator.Instance.AddVisitor(visitor);

var result = CodeModelGenerator.Instance.TypeFactory.CreateEnum(inputEnum);

Assert.IsNotNull(result);
Assert.IsInstanceOf<ExtensibleEnumProvider>(result);
Assert.IsTrue(result!.Type.Namespace.EndsWith(".SomeOtherNamespace"));
}

[Test]
public void CreateEnum_FixedEnumWithoutVisitorsOrCustomCode_ReturnsFixedEnum()
{
// Arrange - Create a fixed enum
var input = InputFactory.StringEnum(
"PlainFixedEnum",
[("value1", "value1"), ("value2", "value2")],
usage: InputModelTypeUsage.Input,
isExtensible: false);

// Act - Create enum without any visitors or custom code modifications
var enumProvider = CodeModelGenerator.Instance.TypeFactory.CreateEnum(input);

// Assert
Assert.IsNotNull(enumProvider);
Assert.IsInstanceOf<FixedEnumProvider>(enumProvider);
Assert.IsFalse(enumProvider!.IsExtensible);
}

[Test]
public void CreateEnum_ExtensibleEnumWithoutVisitorsOrCustomCode_ReturnsExtensibleEnum()
{
// Arrange - Create an extensible enum
var input = InputFactory.StringEnum(
"PlainExtensibleEnum",
[("value1", "value1"), ("value2", "value2")],
usage: InputModelTypeUsage.Input,
isExtensible: true);

// Act - Create enum without any visitors or custom code modifications
var enumProvider = CodeModelGenerator.Instance.TypeFactory.CreateEnum(input);

// Assert
Assert.IsNotNull(enumProvider);
Assert.IsInstanceOf<ExtensibleEnumProvider>(enumProvider);
Assert.IsTrue(enumProvider!.IsExtensible);
}

/// <summary>
/// Test visitor that modifies enum namespaces to end with ".Models"
/// </summary>
private class NamespaceModifyingVisitor : LibraryVisitor
{
protected internal override EnumProvider? PreVisitEnum(InputEnumType enumType, EnumProvider? type)
{
if (type == null)
return type;

// Create a new enum provider with modified namespace
// replace ".Models" with ".SomeOtherNamespace"
var updatedNamespace = type.Type.Namespace.Replace(".Models", ".SomeOtherNamespace");
type.Update(@namespace: updatedNamespace);

return type;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
Expand All @@ -12,6 +12,13 @@ namespace Microsoft.TypeSpec.Generator.Tests.Utilities
{
public class MethodProviderHelpersTests
{

[SetUp]
public void Setup()
{
MockHelpers.LoadMockGenerator();
}

[Test]
public void BuildXmlDocsAddsCorrectExceptions()
{
Expand Down
Loading